Compare commits

...
123 Commits
Author SHA1 Message Date
Alex van Andel 0d03006653 v2.3.3
release.yml / release (push) Canceled after 0s
2022-11-29 09:40:40 +00:00
Alex van Andel d44c5fd8b0 Fixes availability to reset to default on save non-team (#5710) 2022-11-29 09:36:05 +00:00
checkly[bot] 74aa366f1e [Checkly] adds "Location Link" code (#5752)
Co-authored-by: checkly[bot] <54914329+checkly[bot]@users.noreply.github.com>
2022-11-28 16:52:24 -07:00
sean-brydon 7ebea57602 Dialog Api improvements (#5714) 2022-11-28 19:14:38 +00:00
26d675e162 Allow clients to choose different duration for a meeting (#5660)
* WIP

* Missing translation

* Default duration clears correctly

* Fixing hydration error

* Applying feedback

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-28 18:14:01 +00:00
Jeroen Reumkensandkodiakhq[bot] e16d29e916 Added optional chaining to embed iframe using window object, where objects were potentially undefined. (#5727)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-28 09:54:14 -07:00
zomars 0e89fb9f6a Update release.yml 2022-11-28 09:07:30 -07:00
zomars 43fb410a28 v2.3.2
release.yml / release (push) Canceled after 0s
2022-11-28 09:02:55 -07:00
b8bbfe8a3f fix: ui of reschedule dialog (#5691)
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>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-28 08:52:42 -07:00
Leo GiovanettiandAlex van Andel 1f74fc394a Fixing calWarnings error on reschedule (#5711)
* Fixing calWarnings error on reschedule

* Update packages/core/CalendarManager.ts

Co-authored-by: Alex van Andel <me@alexvanandel.com>

* Update packages/core/CalendarManager.ts

Co-authored-by: Alex van Andel <me@alexvanandel.com>

* Going back on potential fix

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-28 15:43:24 +00:00
Carina WollendorferandCarinaWolli 9a12b5a0ac fix e2e tests (#5726)
* fix e2e tests

* remove not needed waitForNavigation

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-11-28 15:27:07 +00:00
Jeroen Reumkens 2ce2c1074b Don't return admin_min error when we're not validating for admin password. (#5713) 2022-11-28 07:57:02 -07:00
Nafees NazikandPeer Richelsen 0ef80da260 fix: remove unnecessary buttons from the image uploader modal (#5725)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-28 14:19:34 +00:00
github-actions[bot]andCrowdin Bot 1e50488cfe New Crowdin translations by Github Action (#5722)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-11-28 12:13:33 +00:00
sean-brydonandPeer Richelsen ae9f66c9ef InputField with suffix select (#5683)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-28 11:20:49 +00:00
798a4b0090 New Crowdin translations by Github Action (#5705)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-27 11:50:40 +00:00
Harsh SinghandGuest dcd9ff72b7 fix banner z-index (#5707)
* fix: 404 page for undefined events

* remove prev code merge

* change: z-index value of banner

Co-authored-by: Guest <guest@pop-os.localdomain>
2022-11-27 11:49:16 +00:00
GitStart 0b5a0186bc [CAL-280] mobile booking: selecting a day does not scroll first time (#5682) 2022-11-26 23:10:31 +00:00
zomars d9846dca4c v2.3.1
release.yml / release (push) Canceled after 0s
2022-11-26 07:16:23 -07:00
zomars 2fd606c38e Fixed circular dependecy that was messing with website 2022-11-26 06:54:55 -07:00
zomars c066a0cba9 Flaky test improvement 2022-11-26 06:54:55 -07:00
Peer Richelsen 568cc22c60 added tracking links to tips (#5690) 2022-11-26 13:10:45 +00:00
b3243c53dc [CAL-440] Font size of text area gets larger on mobile (#5677)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-26 12:50:55 +00:00
alannnc 80ffcf8b58 fix/event-type-location-select (#5692)
* Fixed dialog components usage

* Fix styles
2022-11-25 13:19:08 -07:00
+1 77bf14a797 [CAL-317] Confirmation Email - Footer text isn't properly formatted (#5676)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-25 18:02:30 +00:00
Alex van Andel b188368e8f Defaults to !!eventType.schedule (#5688) 2022-11-25 17:13:42 +00:00
zomars e35631aee3 Update api 2022-11-25 08:11:10 -07:00
Carina WollendorferandCarinaWolli 733a06a672 Show correct cancelled page after cancellation (#5664)
* fix cancelled page and cross out dates

* delete old cancel success page

* fix e2e tests

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-11-25 07:49:59 -07:00
Carina WollendorferandCarinaWolli 2de3e7fc59 Text editor for custom workflow templates (#5654)
* add first version of text editor

* add new editor to email body

* fix line height

* remove not needed styles

* allow adding html tags + small refactoring

* fix error when input is empty

* styling of add variable dropdown

* fix bg on focus

* small design fixes

* improve design

* show error message for no input

* text editor only for email body

* remove test button

* code clean up

* code clean up

* code clean up

* fix that added variables weren't saved

* fix import

* first version of editor

* connect reminder body of form with new editor

* add pencile for link-editor

* remove TreeViewPlugin

* clean up css file

* code clean up

* add text indent for list

* improve editor design

* fix style

* slow resizing of editor

* fix that new data was removed when leaving tab

* dont allow italic and bold at same time

* code clean up

* fix type error

* code clean up

* code clean up

* remove text if changed from SMS to email or other way around

* add add variable dropdown

* show error message if email body is empty

* add white-space pre-wrap

* fix import

* prettier

* fix add variable dropdown for mobile

* move dependencies to correct package.json file

* remove html-to-text

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-11-25 07:34:55 -07:00
zomars 3137a188a0 Type fixes for API 2022-11-25 07:24:44 -07:00
zomars a2a4ff934f Submodule sync 2022-11-25 06:01:25 -07:00
Udit Takkarandsean-brydon eda1d5f393 fix: add team member dialog ui updated (#5675)
* fix: add team member dialog ui updated
fixed team url

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

* fix: type errors and role value bug

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

* chore: remove logs

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

* chore: change option to admin

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: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-11-25 12:59:25 +00:00
sean-brydonandPeer Richelsen 93a14b190e Fix Impersonation Refresh Bug (#5679)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-25 12:36:33 +00:00
Shane MaglangitandPeer Richelsen 2a6dc340df fix: update profile picture to be clickable on cal.com/[user]/[type] (#5596)
* fix: update profile picture to be clickable on /[user]/[type]

* fix: disable clickable profile on private booking pages

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-25 12:12:16 +00:00
Alex van Andel 9cff1e3439 v2.3.0
release.yml / release (push) Canceled after 0s
2022-11-25 11:38:05 +00:00
Leo Giovanettiandalannnc bb9cbd6582 Fixing Zoom timezone issue (#5670)
* Fixing Zoom timezone issue

* Update packages/app-store/zoomvideo/lib/VideoApiAdapter.ts

Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-24 18:11:13 +00:00
81721e7bb9 [CAL-126] onboarding: step 3 not mobile friendly (#5651)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Matheus Muniz <matheusmuniz100@hotmail.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-24 17:40:30 +00:00
Udit Takkar 454311dd52 Edit location dialog new select component (#5647) 2022-11-24 11:53:29 +00:00
Syed Ali Shahbaz 7df04c1d50 TZ Fix for event created on icloud app (#5662) 2022-11-24 11:07:24 +00:00
Udit Takkarandalannnc d9a8ab3864 fix: disable update button when nothing changed (#5592)
* fix: disable update button when nothing changed

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

* fix: invalidate after dsubmission

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

* Fix isDirty state for appearance settings

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-23 23:23:40 +00:00
e3a7fd006b make SMS available for all users again (#5656)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-11-23 22:42:42 +00:00
38d8852819 Fix rescheduling error with attendee phone number as location (#5657)
* only show smsReminder number if event type has SMS_ATTENDEE action

* fix rescheduling bug

* Change array iteration to only run for best scenario

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-23 22:30:54 +00:00
zomars c0f34a39d1 Updates admin seeded password
To comply with admin password requirements
2022-11-23 14:55:18 -07:00
zomars e09dd04e7f Waits for session data before redirecting 2022-11-23 14:55:18 -07:00
1c2c6a62e6 Fix/event type clone (#5659)
* fix: add location query variable

* chore: remove logs

* fix: remove required location

* fix: make locations option

* fix: get location

* Update packages/prisma/zod/custom/eventtype.ts

* Fix location being overrided by default

Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-11-23 18:36:31 -03:00
Udit Takkarandalannnc 33271a88b1 fix: ui of button (#5552)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-23 19:53:12 +00:00
4086a666ae fix: add location query variable (#5540)
* fix: add location query variable

* chore: remove logs

* fix: remove required location

* fix: make locations option

* fix: get location

* Update packages/prisma/zod/custom/eventtype.ts

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-23 12:18:00 -07:00
Alex van AndelandPeer Richelsen b31b8cc6df Upgrade deps + allow node 18.x (#5622)
* Upgrade deps + allow node 18.x

* Upgraded next-auth to v4.17

* Latest known to work version of next-auth

* Make next-auth.d.ts compatible with next-auth@v4.17.0

* Type fixes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-23 11:35:08 -07:00
Carina WollendorferandCarinaWolli 6dce57440e Trigger webhooks when booking is confirmed not submitted (#5609)
* design fix copy api key

* fix webhooks on confirmation + scheduled workflows for confirmed recurring events

* add removed comment

* take rescheduled opt ins into account

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-11-23 16:38:13 +00:00
Peer RichelsenandAlan 22a030afab telegram app (#5643)
* telegram MVP

* Fix app store seed config json

Co-authored-by: Alan <alannnc@gmail.com>
2022-11-23 03:15:23 +00:00
zomars e04d81cf11 We're not ready for this yet 2022-11-22 20:02:40 -07:00
zomars bd6888b758 Submodule sync 2022-11-22 19:56:50 -07:00
Jeroen Reumkensandzomars ccc2bdd25e 🧹 One calcom/ui import to rule them all (#5561)
* Removed emptyscreen component v1 version, migrated pages that still used it to v2, and removed v1 of workflow pages and components.

* updated workflow pages imports to remove v2 from path.

* Deleted v1 switch component, deleted v1 api-keys components, deleted old web integrations components that were unused.

* Removed v1 list component.

* Fixed event workflows tab path.

* Fixed import path for button in sandbox page.

* Cleanup and type fixes

* Making explicit indexes

* UI import migrations

* More import fixes

* More import fixes

* Submodule sync

* Type fixes

* Build fixes

Co-authored-by: zomars <zomars@me.com>
2022-11-22 19:55:25 -07:00
a36b46bd81 Feat/salesforce app (#5649)
* WIP

* warnings and errors, and working app

* Refresh token now usable

* Correcting env.appStore.example

* Reverting changes that will come from Sendgrid App PR

* Resetting with main

* Renaming all othercalendars

* Fixing types

* Renaming leftovers

* More renaming stuff

* Format readme

* Adds prettier override for website wordlist

* Omit salesforce app in this PR

* Revert "Omit salesforce app in this PR"

This reverts commit 22c09fe53578fc37293027ef3667e1bdf2d6c6e7.

* Update yarn.lock

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-22 20:10:31 -03:00
a94d51c4bd Chore/salesforce prep work (#5648)
* WIP

* warnings and errors, and working app

* Refresh token now usable

* Correcting env.appStore.example

* Reverting changes that will come from Sendgrid App PR

* Resetting with main

* Renaming all othercalendars

* Fixing types

* Renaming leftovers

* More renaming stuff

* Format readme

* Adds prettier override for website wordlist

* Omit salesforce app in this PR

* Cleanup

* Update AppSettings.tsx

* Revert "Cleanup"

This reverts commit 41f94c52c3a34ce8ae6047ce9d4c187722343d7e.

* Update yarn.lock

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-22 13:44:08 -07:00
zomars 3854b9c58e Adds prettier override for website wordlist 2022-11-22 13:28:26 -07:00
Peer Richelsenandalannnc ba6963573e added more screenshots and validation (#5645)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-22 19:37:27 +00:00
596f723cff [CAL-395] Change text from "Delete Team" to "Disband Team" on the Button (#5627)
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Murilo Amaral <87545137+MuriloAmarals@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
2022-11-22 19:18:47 +00:00
Peer Richelsen c408d71a53 added whatsapp link MVP app (#5641)
* added whatsapp MVP app

* added another screenshot

* replaced phone number with fake one and added validation
2022-11-22 18:15:16 +00:00
d64400d66b Added proper dark mode support for buttons (#5603)
* Added proper dark mode support for buttons, and converted buttons to use CVA for better maintainable variant styling.

* Added animations to buttons.

* Added cva types to buttonbase type since thats imported in different places

* Fixed issue with styled buttons when false was pas for disabled instead of undefined. Added a small util function that now accepts arrays of variants, and creates all the possible combinations. This way we have less duplicate compoundvariants defined. This fixes the styles in the eventsinglelayout component.

* Undo disabling of api jest tests.

* Fixed remaining buttons using combined prop, which is replace by button group.

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-11-22 17:07:55 +00:00
Peer Richelsen 16eede9bb0 added primary color to payment button for stripe bookings (#5638) 2022-11-22 16:38:25 +00:00
alannncandkodiakhq[bot] ba5d448811 fix formState and undefined value (#5576)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-22 16:27:47 +00:00
Syed Ali Shahbaz 1070cc2892 Attempt to fix vercel edge crash (#5636)
* reverts PR with windows support

* adds windows specific check and return
2022-11-22 13:33:17 +00:00
Udit TakkarandPeer Richelsen df91425643 fix: correct month after changing time zone (#5635)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-22 13:09:23 +00:00
Peer Richelsen c98bafcd52 fixed icon on booking page for link locations (#5634) 2022-11-22 12:04:06 +00:00
sean-brydonandAlex van Andel 9441a79def Booking Limits checking ALL events not the event the limit is applied too (#5601)
* Fix error message

* Only apply buffer limits to That event type

* Correctly get eventtypeId in source

* Add comment

* Move to i188n strings

* Wrap string to be translated

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-22 03:17:54 +00:00
Peer Richelsen 994df45a19 added requires confirmation to Tips.tsx (#5535) 2022-11-21 20:09:05 +00:00
Udit TakkarandPeer Richelsen c4662d79e1 fix: remove px and change min width in suffix (#5584)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-21 19:20:33 +00:00
Carina WollendorferandCarinaWolli 50cd1fbd83 Skeleton loader improvements (#5614)
* improve skeleton loaders

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-11-21 19:05:49 +00:00
Syed Ali Shahbaz 196549c124 Adds windows compatibility for copywebpackplugin (#5602)
* adds windows compatibility

* Revert unintentional change
2022-11-21 14:33:19 +00:00
Udit TakkarandPeer Richelsen 91274d8ee0 fix: change admin user password requirement (#5577)
* fix: change admin user password requirement

* fix: preload translations

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-21 09:03:45 +01:00
Leo Giovanetti fa026ee393 Fixing Node.js version to v16.x (#5591)
* Fixing Node.js version to v16.x

* Updating web app
2022-11-20 21:13:33 +00:00
zomars 5793901b03 Adds conditions for upgrading team from API 2022-11-18 16:55:34 -07:00
zomars 030f1c6990 Preparations so public API can use tRPC procedures 2022-11-18 16:36:32 -07:00
René MüllerandRené Müller b201067f86 enable team billing when HOSTED_CAL_FEATURES isset (#5516)
* enable team billing when `HOSTED_CAL_FEATURES` isset

* changed comment above of `IS_TEAM_BILLING_ENABLED` in constants.ts

Co-authored-by: René Müller <rene.mueller@clicksports.de>
2022-11-18 11:59:01 -07:00
alannncandJoe Au-Yeung cb96b12108 Add conditional if response received from graph api v1 was empty (#5573)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-11-18 11:41:36 -06:00
Alex van Andel e7f557f7f9 v2.2.2
release.yml / release (push) Canceled after 0s
2022-11-18 14:52:34 +00:00
Alex van Andel e2921c3126 Techdebt/improve sentry config (#5585)
* Fix console errors during build

* Create source maps for production
2022-11-18 14:23:14 +00:00
Jeroen Reumkens d8102615b1 Upgrade Sentry to latest to add excludeServerRoutes in order to exclude OG Edge endpoint since Sentry won't work on the Edge yet, but WILL crash the api route. (#5574) 2022-11-18 09:49:19 +00:00
1010e2894a Bringing back sendgrid app to review (#5501)
* Sendgrid app and code simplification

* Applying app-store-cli + impl

* Fixing types

* Adding features to readme

* Fixing unit tests

* A few last tweaks regarding UX and env vars

* Applying feedback

* Using calcom icons

* Renaming and applying feedback

* Testing user/type page fix

* Standarizing Sendgrid client usage

* Removing types

* Reverting CloseCom changes

* Stop relying on sendgrid client pkg

* Fixing button and more reverting closecom changes

* Revert "Stop relying on sendgrid client pkg"

This reverts commit dd61851572a17a1e4051b133683af85c934bc2d0.

* Revert "Removing types"

This reverts commit 1ec5ed8de2f3139bbe84f867f229bc5759256806.

* Is this it?

* Standardizing apis

* Fixing path

* Fixing throwing errors the standard way

* Stop relying on getInstalledAppPath

* Removing seemingly troubling code

* Returning error and avoiding any outer reference

* Revert "Returning error and avoiding any outer reference"

This reverts commit 7d32e30154423c95f54ebae81a76ab16a1c7bc94.

* Revert "Removing seemingly troubling code"

This reverts commit eaae772abcd04c8f046e4960116f42c5aaf87adf.

* Revert "Stop relying on getInstalledAppPath"

This reverts commit bcc70fc337bbe7fb5e74609abaeee7cd3ede90a3.

* Revert "Fixing throwing errors the standard way"

This reverts commit bb1bb410fac6f8c6ad14c3163a8433d125f7a885.

* Revert "Fixing path"

This reverts commit a7bd83c4fb7597594d0470cb530378c826b45481.

* Revert "Standardizing apis"

This reverts commit 0258a182298af3ebad321854ef4f34a65f4c700a.

* Revert "Is this it?"

This reverts commit 70b3f7b98e3003dfa225dc539e02a1e17abdd840.

* Converting APIs to legacy style

* Missing reverted CloseCom test mock

* Needed for the renaming

* Reverting Closecom and yarn unneeded changes

* Ununsed type

* Testing rearranging exports

* Update apps/web/components/apps/OmniInstallAppButton.tsx

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

* Standardizing APIs

* Fixing wrong toast message on app page

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-11-17 21:38:34 +00:00
Udit TakkarandAlex van Andel c728174c14 fix: return when success status 204 (#5562)
* fix: return when success status 204

* fix: handle login in handleZoomResponse

* Add response.status check after the response.ok check

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-17 21:27:22 +00:00
Udit TakkarandPeer Richelsen 3889b49fa1 fix: use zod for validation (#5498)
* fix: use zod for validation

* fix: use slug instead of url

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-17 18:56:47 +00:00
sean-brydon 461389fb0b increase limits (#5554) 2022-11-17 09:17:07 +00:00
Jeroen Reumkens a8186e3039 Fixed wrong calculation of duration type on event type page. (#5557) 2022-11-17 13:38:03 +05:30
zomars 5e2db12396 Update release.yml 2022-11-16 15:17:35 -07:00
zomars 6bc9d2fd94 Update release.yml 2022-11-16 15:06:20 -07:00
zomars 37f9bbf3fa v2.2.1
release.yml / release (push) Canceled after 0s
2022-11-16 15:01:57 -07:00
zomars fb5b3bbcc6 Fixes team members avatar shrinkage 2022-11-16 15:01:25 -07:00
zomars d43ee154d0 Update release.yml 2022-11-16 14:38:15 -07:00
zomars 6415d42a1d v2.2.0
release.yml / release (push) Canceled after 0s
2022-11-16 14:22:08 -07:00
Omar Lópezandalannnc ee620c7fff Adds team upgrade banner (#5541)
* Adds team upgrade banner

* Update TeamListItem.tsx

* Update payments.ts

* Copy updates and mobile style fixes

* Update packages/trpc/server/routers/viewer/teams.tsx

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

Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-16 21:07:20 +00:00
Jeroen Reumkens 112efbbda2 CAL-300 Ensure we show the default calendar name based in the advanced tab for event types. (#5551) 2022-11-16 20:03:19 +00:00
fac0b0fa32 Improving cancellation flow (#5447)
* Booking succes query refactor

The query is now using the uid as its main identifier for the success page

* Minor changes to the succes.tsx and tests

* Convert eventtype dates to string, and only select eventtype slug from db to have a smaller query (we don't need more data, and this way we don't need to convert the dates in here to smaller strings either.)

* In the payment component get the bookingUid from props instead of the query

* design fixes

* fix UI for recurring bookings

* fix success page for recurring events

* fix cancelling recurring events

* fix design of successful cancellation page

* fix email redirect + design fixes

* remove old cancel page

* fix v2 design for text area

* Changed the recurringMutation to use the uid for the success booking page

* redirect form old to new cancel page

* fix success page for recurring events

* rename to allRemainingbookings

* fix old cancel redirect

* fix recurring cancel link in email

* fix redirect when cancelling one recurring booking

* remove reschedule for recurring events in email

* create queryschema for cancel success page

* scroll down to bottom if cancel

* add cancellation reason

* fix tests

* fix old /cancel component

* code clean up

* Uses URL query as cancellation state

Also fixes string to boolean inference

Co-authored-by: Mischa Rouleaux <mischa-rouleaux@live.nl>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2022-11-16 19:48:17 +00:00
Jeroen Reumkens 12d47ab949 Update UI components to be able to use them in Console (#5514)
* First steps in updating console ui components.

* Replaced remaining v1 components.

* Removed todo.

* Removed createelement from form component since we were not using it at all (we wrapped all our inputs in divs), and any input that was not wrapped, was actually broken...

* Undo update submodule.

* Reverted website submodule update.
2022-11-16 13:41:40 +00:00
4fef7ba5e2 Feat/top banner (#5443)
* wip

* wip

* chore: remove unused import

* feat: top banner component added

* feat: top banner stories added

* fix: icon bug

* Update packages/ui/components/topbanner/TopBanner.tsx

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

* Apply suggestions from code review

* Export fixes

* Feedback

* Type fixes

* Fixes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-15 12:33:59 -07:00
6af0428a18 Booking success query refactor (#5298)
* Booking succes query refactor

The query is now using the uid as its main identifier for the success page

* Minor changes to the succes.tsx and tests

* Convert eventtype dates to string, and only select eventtype slug from db to have a smaller query (we don't need more data, and this way we don't need to convert the dates in here to smaller strings either.)

* In the payment component get the bookingUid from props instead of the query

* Changed the recurringMutation to use the uid for the success booking page

Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-15 12:00:02 -07:00
Jeroen Reumkensandkodiakhq[bot] d66f3d1dc9 Event row style improvements (#5533)
* Made full row of event type clickable, and fixed event type loader dimensions.

* Fixed skeletons for teams and workflows.

* Fixed skeleton for workflow detail page.

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-15 15:30:24 +00:00
Udit Takkar a5249b6cb5 fix: use tooltip and max-w (#5531) 2022-11-15 14:24:23 +00:00
Udit Takkar 6771c11360 fix: remove type password from fields (#5530) 2022-11-15 14:08:25 +00:00
002375ac72 fix langauge in booking submitted email for attendee (#5526)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-15 13:55:02 +00:00
Alex van Andel 0402e46328 Reformat /booking attendees, add guests to success page, fix i18n (#5532) 2022-11-15 13:39:06 +00:00
Jeroen ReumkensandPeer Richelsen 02e3738a17 Improved event description truncation (actually removed it mostly) (#5374)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-15 08:36:20 +00:00
Alex van Andel f822177788 Use Intl.DateTimeFormat logic to determine locale. (#5519) 2022-11-14 19:46:58 +00:00
Alex van Andel 1edc9c50d5 Removed an invalidate onDelete + updated test (#5521) 2022-11-14 19:34:47 +00:00
Syed Ali Shahbaz 7beb17c007 stops content pushing on windows browsers due to overflow-y (#5513) 2022-11-14 11:07:44 +00:00
Hariom Balharaandkodiakhq[bot] ad7af451ff Fix text getting changed after browsing through Reporting tab (#5510)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-14 10:53:35 +00:00
f513ce8687 Removed unused ui components and updated skeleton components (#5412)
* Removed unused ui components and updated skeleton components throughout the app to use new version.

* Deleted unused app component and changed destructive button variants to secondary as per design.

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-14 10:31:36 +00:00
d83883e7e4 Minimum booking notice will allow hours and days (#5217)
* minimum booking notice allows hours and days

* Fixed error with Prisma not updating properly and input changing value to minutes autmatically

* fix type error

* Update apps/web/components/v2/eventtype/EventLimitsTab.tsx

Co-authored-by: Peer Richelsen <peer@cal.com>

* moved minimumBookingNotice to clientside, removed migration from prisma, changed DurationField to regular InputField

* Update packages/lib/convertMinimumBookingNoticeToMinutes.ts

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

* added MINUTES_IN_HOUR, MINUTES_IN_DAY, and HOURS_IN_DAY as exportable consts

* Fix some comparison, mobile styles and const usages

* minimumBookingNotice values will never be floats

* fixed issue with minbookingnotice not updating properly in prisma

* Removed console.logs, converted minimumBookingNoticeType to ref, removed unreliable accessing of array

* remove yarn lockfile

* Revert "remove yarn lockfile"

This reverts commit fefc24c80269f5445c8eb7a640e2816d7d8b0407.

* Revert "Revert "remove yarn lockfile""

This reverts commit c8cc4d3c0eddb2c91d077d2277ad5ea10315ec95.

* Replaced currentDurationType export with state passed down from parent

* Improvements for duration notice type logic

* Undo adding submodule updates

* Update apps/web/components/eventtype/EventLimitsTab.tsx

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

* Update packages/lib/findDurationType.ts

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

* Update apps/web/components/eventtype/EventLimitsTab.tsx

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

* Update packages/lib/convertToNewDurationType.ts

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

* Update apps/web/components/eventtype/EventLimitsTab.tsx

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

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

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

* remove passing in as object

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

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

* Update packages/lib/convertToNewDurationType.ts

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

* Delete convertMinimumBookingNoticeToMinutes.ts

* Syntax fix

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
2022-11-14 09:12:28 +00:00
9e001c53e8 add tests (#5487)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-14 08:50:09 +00:00
Hariom BalharaandPeer Richelsen 3937b0c4c6 Add support to open a redirect URL in new tab (#5480)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-13 15:51:31 +00:00
Udit TakkarandPeer Richelsen cbe358bb6b fix: booking rejection message (#5497)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-12 21:21:01 +00:00
Leo Giovanetti 2b91102f3f Recurring emails fixed (#5499) 2022-11-12 16:46:27 +00:00
Udit Takkar 4e7804c46d fix: change max width for mobile (#5495) 2022-11-12 13:43:04 +00:00
Udit Takkar d7226fc3d4 fix: disable button and handle submit when loading (#5496) 2022-11-12 13:31:11 +00:00
Alex van Andel 0cb278d109 Update engines to >=16.x in apps/web and monorepo (#5493) 2022-11-11 21:35:38 +00:00
alannncandOmar López a1430101d1 fix/team-billing-update-publish-form (#5476)
* Display team slug on form when saved on metadata

* remove button disabled prop

* update mutation to latest trcp style

* Update packages/trpc/server/routers/viewer/teams.tsx

Co-authored-by: Omar López <zomars@me.com>
2022-11-11 20:38:21 +00:00
9092addd9c fix: use .watch() to disable update btn (#5482)
* fix: use .watch() to disable update btn

* Apply suggestions from code review

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-11 20:27:09 +00:00
Masumi Kawasakiandalannnc 7f14e00119 Correction of unclear Japanese translation (#5455)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-11-11 20:05:46 +00:00
Omar Lópezandkodiakhq[bot] abc5ff2d97 Deprecated API endpoints cleanup (#5478)
* Deprecated API endpoints cleanup

* Linting

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-11 19:54:44 +00:00
Udit TakkarandPeer Richelsen fcde1f7a9d fix: ui bugs in apps (#5465)
* fix: ui bugs in apps

* fix: recurring text

* fix: logo size and font

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-11 19:35:10 +00:00
Udit TakkarandOmar López 30eea089ba fix: use outline none (#5486)
* fix: use outline none

* fix: dropdown trigger ui bugs

* Update packages/ui/Dropdown.tsx

Co-authored-by: Omar López <zomars@me.com>
2022-11-11 12:21:56 -07:00
zomars 3c8cb3a9ea Upgaded Snaplet CLI 2022-11-11 10:32:35 -07:00
54f4e665a3 Add alphanumeric sender ID to SMS workflow actions (#5471)
* add sender id

* add sender to twilio from

* added missing sender

* add migration

* fix design of add action dialog

* add cal as sender when creating new workflow

* fix type errors

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-11 15:01:17 +00:00
618 changed files with 11713 additions and 10155 deletions
+10 -1
View File
@@ -11,6 +11,10 @@
# - ZOOM
# - GIPHY
# - VITAL
# - ZAPIER
# - LARK
# - WEB3
# - SALESFORCE
# - APP STORE **********************************************************************************************
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
@@ -88,7 +92,7 @@ VITAL_REGION="us"
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/zapier/README.md
ZAPIER_INVITE_LINK=""
# - LARK
# - LARK
# Needed to enable Lark Calendar integration and Login with Lark
# @see <https://open.larksuite.com/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/g>
LARK_OPEN_APP_ID=""
@@ -100,4 +104,9 @@ LARK_OPEN_VERIFICATION_TOKEN=""
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/rainbow/README.md
ALCHEMY_API_KEY=""
INFURA_API_KEY=""
# - SALESFORCE
# Used for the Salesforce (Sales Cloud) app
SALESFORCE_CONSUMER_KEY=""
SALESFORCE_CONSUMER_SECRET=""
# *********************************************************************************************************
+3 -3
View File
@@ -77,7 +77,7 @@ NEXT_PUBLIC_HELPSCOUT_KEY=
SEND_FEEDBACK_EMAIL=
# Sengrid
# Used for email reminders in workflows
# Used for email reminders in workflows and internal sync services
SENDGRID_API_KEY=
SENDGRID_EMAIL=
@@ -134,8 +134,8 @@ NEXT_PUBLIC_TEAM_IMPERSONATION=false
# Close.com internal CRM
CLOSECOM_API_KEY=
# Sendgrid internal email sender
SENDGRID_API_KEY=
# Sendgrid internal sync service
SENDGRID_SYNC_API_KEY=
# Sentry
NEXT_PUBLIC_SENTRY_DSN=
+7 -1
View File
@@ -1,4 +1,5 @@
on:
workflow_dispatch:
push:
# Pattern matched against refs/tags
tags:
@@ -8,4 +9,9 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- run: curl -X POST -d {} ${{ secrets.VERCEL_VERSIONED_DEPLOY_HOOK }}
- uses: actions/checkout@v3
with:
ref: main # Always checkout main even for tagged releases
fetch-depth: 0
token: ${{ secrets.GH_ACCESS_TOKEN }}
- run: git push origin +main:production
+1 -1
View File
@@ -1,4 +1,4 @@
{
"projectId": "cl4u26bwz7962859ply7ibuo43t",
"databaseUrl": "postgresql://postgres@localhost:5450/calendso"
"targetDatabaseUrl": "postgresql://postgres@localhost:5450/calendso"
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"version": "0.3.0"
"version": "0.22.3"
}
+2 -1
View File
@@ -6,5 +6,6 @@
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"spellright.language": ["en"],
"spellright.documentTypes": ["markdown", "typescript"]
"spellright.documentTypes": ["markdown", "typescript"],
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
}
+1 -1
View File
@@ -387,7 +387,7 @@ following
4. Fill in any information you want in the "App info" tab
5. Go to tab "Auth"
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspotothercalendar/callback` replacing Cal.com URL with the URI at which your application runs.
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot/callback` replacing Cal.com URL with the URI at which your application runs.
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
9. Click the "Save" button at the bottom footer.
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
+35
View File
@@ -0,0 +1,35 @@
/**
* This is a Playwright script Checkly generated for you based on your Vercel project.
* To learn more about Browser checks and Playwright visit: https://www.checklyhq.com/docs/browser-checks
*/
// Create a Chromium browser
const { chromium } = require("playwright");
// Checkly supports top level await, but we wrap your code in an async function so you can run it locally too.
async function run() {
const browser = await chromium.launch();
const page = await browser.newPage();
const targetUrl = process.env.ENVIRONMENT_URL || "https://cal.com";
await page.goto(`${targetUrl}/rick/test-location-link`);
await page.waitForSelector('[data-testid="day"][data-disabled="false"]');
await page.click('[data-testid="day"][data-disabled="false"]');
await page.waitForSelector('[data-testid="time"]');
await page.click('[data-testid="time"]');
await page.waitForSelector("#name");
await page.click("#name");
await page.type("#name", "Calcom");
await page.type('[name="email"]', "cal@cal.com");
await page.waitForSelector('[data-testid="confirm-book-button"]');
await page.click('[data-testid="confirm-book-button"]');
await browser.close();
}
run();
+4
View File
@@ -50,6 +50,10 @@
"description": "Sendgrid api key. Used for email reminders in workflows",
"value": ""
},
"SENDGRID_SYNC_API_KEY": {
"description": "Sendgrid internal sync service",
"value": ""
},
"SENDGRID_EMAIL": {
"description": "Sendgrid email. Used for email reminders in workflows",
"value": ""
+4 -3
View File
@@ -3,11 +3,12 @@ import { classNames } from "@calcom/lib";
interface ExampleProps {
children: React.ReactNode;
title: string;
isFullWidth?: boolean;
}
export const Example = ({ children, title }: ExampleProps) => {
export const Example = ({ children, title, isFullWidth = false }: ExampleProps) => {
return (
<div className="examples-item">
<span className="examples-item-title">{title}</span>
<div className={classNames("examples-item", isFullWidth && "w-full")}>
<span className={classNames("examples-item-title", isFullWidth && "mb-0 mt-2")}>{title}</span>
<div className="examples-item-content">{children}</div>
</div>
);
+1 -1
View File
@@ -19,7 +19,7 @@ export function VariantsTable({
<div
className={classNames(
isDark &&
"relative py-8 before:absolute before:left-0 before:top-0 before:block before:h-full before:w-screen before:bg-[#22252A]"
"relative py-8 before:absolute before:left-0 before:top-0 before:block before:h-full before:w-screen before:bg-[#1C1C1C]"
)}>
<div className="z-1 relative mx-auto w-full max-w-[1200px] overflow-auto pr-8 pt-6">
<table>
-15
View File
@@ -1,15 +0,0 @@
declare module "@wojtekmaj/react-daterange-picker/dist/entry.nostyle" {
import { CalendarProps } from "react-calendar";
export type DateRangePickerCalendarProps = Omit<
CalendarProps,
"calendarClassName" | "onChange" | "value"
> & {
calendarClassName?: string;
onChange: (value: [Date, Date]) => void;
value: [Date, Date];
clearIcon: JSX.Element | null;
calendarIcon: JSX.Element | null;
rangeDivider: JSX.Element | null;
};
export default function DateRangePicker(props: DateRangePickerCalendarProps): JSX.Element;
}
-119
View File
@@ -598,60 +598,6 @@ paths:
summary: Gets and stores the OAuth token
tags:
- Integrations
/api/user/profile:
patch:
description: Updates a user's profile.
summary: Updates a user's profile
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
description:
type: string
avatar:
type: string
timeZone:
type: string
weekStart:
type: string
hideBranding:
type: string
theme:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
message:
type: string
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
properties:
message:
type: string
"500":
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
/api/me:
get:
description: Gets current user's profile.
@@ -686,71 +632,6 @@ paths:
properties:
message:
type: string
/api/user/membership:
get:
description: Get a list of the teams the user has joined.
summary: Get the teams a user is joined to
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
message:
type: string
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
properties:
message:
type: string
"500":
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
patch:
description: Accept team invitation
summary: Accept team invitation.
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
properties:
teamId:
type: string
delete:
description: Leave team or decline membership invite of current user
summary: Leave team or decline team invite.
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
properties:
teamId:
type: string
"/api/{team}":
delete:
description: Deletes a team
+1 -1
View File
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Icon } from "@calcom/ui";
export default function AddToHomescreen() {
const { t } = useLocale();
-309
View File
@@ -1,309 +0,0 @@
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { InstallAppButton } from "@calcom/app-store/components";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App";
import { Button, SkeletonButton } from "@calcom/ui";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import { Badge } from "@calcom/ui/components/badge";
import showToast from "@calcom/ui/v2/core/notifications";
import DisconnectIntegration from "@components/integrations/DisconnectIntegration";
const Component = ({
name,
type,
logo,
body,
categories,
author,
price = 0,
commission,
isGlobal = false,
feeType,
docs,
website,
email,
tos,
privacy,
isProOnly,
}: Parameters<typeof App>[0]) => {
const { t } = useLocale();
const router = useRouter();
const { data: user } = trpc.viewer.me.useQuery();
const utils = trpc.useContext();
const handleOpenChange = () => {
utils.viewer.integrations.invalidate();
router.replace("/apps/installed");
};
const mutation = useAddAppMutation(null, {
onSuccess: () => {
showToast("App successfully installed", "success");
},
onError: (error) => {
if (error instanceof Error) showToast(error.message || "App could not be installed", "error");
},
});
const priceInDollar = Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price);
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
const appCredentials = trpc.viewer.appCredentialsByType.useQuery(
{ appType: type },
{
onSuccess(data) {
setExistingCredentials(data);
},
}
);
const allowedMultipleInstalls = categories.indexOf("calendar") > -1;
return (
<div className="-mx-4 md:-mx-8">
<div className="px-8">
<Link href="/apps">
<a className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800">
<Icon.FiChevronLeft className="h-5 w-5" /> {t("browse_apps")}
</a>
</Link>
<div className="items-center justify-between py-4 sm:flex sm:py-8">
<div className="flex">
<img className="h-16 w-16 rounded-sm" src={logo} alt={name} />
<header className="px-4 py-2">
<div className="flex items-center">
<h1 className="font-cal text-xl text-gray-900">{name}</h1>
</div>
<h2 className="text-sm text-gray-500">
<span className="capitalize">{categories[0]}</span> • {t("published_by", { author })}
</h2>
</header>
</div>
<div className="mt-4 sm:mt-0 sm:text-right">
{!appCredentials.isLoading ? (
isGlobal ||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
<div className="flex space-x-3">
<Button StartIcon={Icon.FiCheck} color="secondary" disabled>
{existingCredentials.length > 0
? t("active_install", { count: existingCredentials.length })
: t("globally_install")}
</Button>
{!isGlobal && (
<InstallAppButton
type={type}
isProOnly={isProOnly}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
onClick: () => {
mutation.mutate({ type });
},
loading: mutation.isLoading,
};
}
return (
<Button StartIcon={Icon.FiPlus} {...props} data-testid="install-app-button">
{t("add_another")}
</Button>
);
}}
/>
)}
</div>
) : existingCredentials.length > 0 ? (
<DisconnectIntegration
id={existingCredentials[0]}
render={(btnProps) => (
<Button {...btnProps} color="warn" data-testid={type + "-integration-disconnect-button"}>
{t("disconnect")}
</Button>
)}
onOpenChange={handleOpenChange}
/>
) : (
<InstallAppButton
type={type}
isProOnly={isProOnly}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
onClick: () => {
mutation.mutate({ type });
},
loading: mutation.isLoading,
};
}
return (
<Button data-testid="install-app-button" {...props}>
{t("install_app")}
</Button>
);
}}
/>
))
) : (
<SkeletonButton width="24" height="10" />
)}
{price !== 0 && (
<small className="block text-right">
{feeType === "usage-based" ? commission + "% + " + priceInDollar + "/booking" : priceInDollar}
{feeType === "monthly" && "/" + t("month")}
</small>
)}
</div>
</div>
</div>
<div className="justify-between px-8 py-10 md:flex">
<div className="prose-sm prose mb-6">{body}</div>
<div className="md:max-w-80 flex-1 md:ml-8">
<h4 className="font-medium text-gray-900 ">{t("categories")}</h4>
<div className="space-x-2">
{categories.map((category) => (
<Link href={"/apps/categories/" + category} key={category}>
<a>
<Badge variant="success">{category}</Badge>
</a>
</Link>
))}
</div>
<h4 className="mt-8 font-medium text-gray-900 ">{t("pricing")}</h4>
<small>
{price === 0 ? (
"Free"
) : (
<>
{Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price)}
{feeType === "monthly" && "/" + t("month")}
</>
)}
</small>
<h4 className="mt-8 mb-2 font-medium text-gray-900 ">{t("learn_more")}</h4>
<ul className="prose -ml-1 -mr-1 text-xs leading-5">
{docs && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={docs}>
<Icon.FiBookOpen className="mr-1 -mt-1 inline h-4 w-4" />
{t("documentation")}
</a>
</li>
)}
{website && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={website}>
<Icon.FiExternalLink className="mr-1 -mt-px inline h-4 w-4" />
{website.replace("https://", "")}
</a>
</li>
)}
{email && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={"mailto:" + email}>
<Icon.FiMail className="mr-1 -mt-px inline h-4 w-4" />
{email}
</a>
</li>
)}
{tos && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={tos}>
<Icon.FiFile className="mr-1 -mt-px inline h-4 w-4" />
{t("terms_of_service")}
</a>
</li>
)}
{privacy && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={privacy}>
<Icon.FiShield className="mr-1 -mt-px inline h-4 w-4" />
{t("privacy_policy")}
</a>
</li>
)}
</ul>
<hr className="my-6" />
<small className="leading-1 block text-gray-500">
Every app published on the Cal.com App Store is open source and thoroughly tested via peer
reviews. Nevertheless, Cal.com, Inc. does not endorse or certify these apps unless they are
published by Cal.com. If you encounter inappropriate content or behaviour please report it.
</small>
<a className="mt-2 block text-xs text-red-500" href="mailto:help@cal.com">
<Icon.FiFlag className="inline h-3 w-3" /> Report App
</a>
</div>
</div>
</div>
);
};
export default function App(props: {
name: string;
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
body: React.ReactNode;
categories: string[];
author: string;
pro?: boolean;
price?: number;
commission?: number;
feeType?: AppType["feeType"];
docs?: string;
website?: string;
email: string; // required
tos?: string;
privacy?: string;
licenseRequired: AppType["licenseRequired"];
isProOnly: AppType["isProOnly"];
}) {
return (
<Shell large isPublic>
{props.licenseRequired ? (
<LicenseRequired>
<Component {...props} />
</LicenseRequired>
) : (
<Component {...props} />
)}
</Shell>
);
}
+17 -10
View File
@@ -5,11 +5,20 @@ import { createRef, forwardRef, MutableRefObject, RefObject, useRef, useState }
import { components, ControlProps } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { HorizontalTabs, showToast, Switch } from "@calcom/ui/v2";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/v2/core/Dialog";
import {
Button,
Dialog,
DialogClose,
DialogContent,
HorizontalTabs,
Icon,
InputLeading,
Label,
showToast,
Switch,
TextArea,
TextField,
} from "@calcom/ui";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
@@ -610,7 +619,7 @@ const ChooseEmbedTypesDialogContent = () => {
const { t } = useLocale();
const router = useRouter();
return (
<DialogContent type="creation" useOwnActionButtons size="lg">
<DialogContent type="creation" size="lg">
<div className="mb-4">
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
{t("how_you_want_add_cal_site")}
@@ -788,7 +797,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
];
return (
<DialogContent size="xl" className="p-0.5" type="creation" useOwnActionButtons>
<DialogContent size="xl" className="p-0.5" type="creation">
<div className="flex">
<div className="flex w-1/3 flex-col bg-gray-50 p-8">
<h3 className="mb-2 flex text-xl font-bold leading-6 text-gray-900" id="modal-title">
@@ -1086,9 +1095,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
{t("copy_code")}
</Button>
) : null}
<DialogClose asChild>
<Button color="secondary">{t("Close")}</Button>
</DialogClose>
<DialogClose />
</div>
</div>
);
+3 -8
View File
@@ -1,8 +1,7 @@
import { FormEvent, useCallback, useEffect, useState } from "react";
import Cropper from "react-easy-crop";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui";
import { Area, getCroppedImg } from "@lib/cropImage";
import { useFileReader } from "@lib/hooks/useFileReader";
@@ -157,12 +156,8 @@ export default function ImageUploader({
</div>
</div>
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose asChild>
<Button onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</Button>
</DialogClose>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<DialogClose onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</DialogClose>
<DialogClose color="secondary">{t("cancel")}</DialogClose>
</div>
</DialogContent>
</Dialog>
+1 -1
View File
@@ -1,4 +1,4 @@
/**
* @deprecated Use custom Skeletons instead
**/
export { default } from "@calcom/ui/Loader";
export { Loader as default } from "@calcom/ui";
+1 -3
View File
@@ -1,8 +1,6 @@
import React, { ComponentProps } from "react";
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import { ErrorBoundary, Icon, Shell } from "@calcom/ui";
import NavTabs from "./NavTabs";
@@ -1,13 +1,10 @@
import React from "react";
import { OptionProps } from "react-select";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui";
import { Icon } from "@calcom/ui/Icon";
import { Select } from "@calcom/ui/v2";
import { Button, Icon, Select } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
+14 -6
View File
@@ -9,11 +9,15 @@ import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { showToast, SkeletonText } from "@calcom/ui/v2";
import { SkeletonButton, Shell } from "@calcom/ui/v2";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import {
Button,
DisconnectIntegration,
Icon,
Shell,
showToast,
SkeletonButton,
SkeletonText,
} from "@calcom/ui";
import HeadSeo from "@components/seo/head-seo";
@@ -43,7 +47,8 @@ const Component = ({
const router = useRouter();
const mutation = useAddAppMutation(null, {
onSuccess: () => {
onSuccess: (data) => {
if (data.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
@@ -122,6 +127,7 @@ const Component = ({
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
onClick: () => {
mutation.mutate({ type, variant, slug });
},
@@ -146,6 +152,7 @@ const Component = ({
</div>
) : existingCredentials.length > 0 ? (
<DisconnectIntegration
buttonProps={{ color: "secondary" }}
label={t("disconnect")}
credentialId={existingCredentials[0]}
onSuccess={() => {
@@ -159,6 +166,7 @@ const Component = ({
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
onClick: () => {
mutation.mutate({ type, variant, slug });
},
@@ -6,15 +6,18 @@ import { InstallAppButton } from "@calcom/app-store/components";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
import { Button } from "@calcom/ui/components";
import { Alert, EmptyScreen } from "@calcom/ui/v2";
import { List } from "@calcom/ui/v2/core/List";
import { ShellSubHeading } from "@calcom/ui/v2/core/Shell";
import Switch from "@calcom/ui/v2/core/Switch";
import showToast from "@calcom/ui/v2/core/notifications";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import {
Alert,
Button,
DisconnectIntegration,
EmptyScreen,
Icon,
List,
ShellSubHeading,
showToast,
SkeletonLoader,
Switch,
} from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
@@ -3,8 +3,7 @@ import { useRouter } from "next/router";
import { ReactNode, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/core/List";
import { Icon, ListItem, ListItemText, ListItemTitle, showToast } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -27,6 +26,13 @@ function IntegrationListItem(props: {
const [highlight, setHighlight] = useState(hl === props.slug);
const title = props.name || props.title;
// The highlight is to show a newly installed app, coming from the app's
// redirection after installation, so we proceed to show the corresponding
// message
if (highlight) {
showToast(t("app_successfully_installed"), "success");
}
useEffect(() => {
const timer = setTimeout(() => setHighlight(false), 3000);
return () => {
+1 -1
View File
@@ -3,7 +3,7 @@ import useDigitInput from "react-digit-input";
import { useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Label, Input } from "@calcom/ui/form/fields";
import { Label, Input } from "@calcom/ui";
export default function TwoFactor({ center = true }) {
const [value, onChange] = useState("");
@@ -1,6 +1,6 @@
import React from "react";
import { SkeletonText } from "@calcom/ui/v2";
import { SkeletonText } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -1,6 +1,6 @@
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import Tooltip from "@calcom/ui/v2/core/Tooltip";
import { Icon, Tooltip } from "@calcom/ui";
import { Props } from "./pages/AvailabilityPage";
@@ -15,14 +15,18 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
}
return (
<div key={location.type} className="flex flex-row items-center text-sm font-medium">
<img
src={eventLocationType.iconUrl}
className={classNames(
"mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 ",
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
{eventLocationType.iconUrl === "/link.svg" ? (
<Icon.FiLink className="dark:text-darkgray-600 mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 " />
) : (
<img
src={eventLocationType.iconUrl}
className={classNames(
"mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 ",
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
)}
<Tooltip content={locationKeyToString(location)}>
<p className="truncate">{locationKeyToString(location)}</p>
</Tooltip>
@@ -7,8 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TimeFormat } from "@calcom/lib/timeFormat";
import { nameOfDay } from "@calcom/lib/weekday";
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
import { ToggleGroup } from "@calcom/ui/v2/core/form/ToggleGroup";
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
import classNames from "@lib/classNames";
import { timeZone } from "@lib/clock";
@@ -152,13 +151,13 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
{isLoading && !slots.length && (
<>
<SkeletonContainer className="mb-2">
<SkeletonText width="full" height="20" />
<SkeletonText className="h-5 w-full" />
</SkeletonContainer>
<SkeletonContainer className="mb-2">
<SkeletonText width="full" height="20" />
<SkeletonText className="h-5 w-full" />
</SkeletonContainer>
<SkeletonContainer className="mb-2">
<SkeletonText width="full" height="20" />
<SkeletonText className="h-5 w-full" />
</SkeletonContainer>
</>
)}
@@ -1,9 +1,11 @@
import { SchedulingType } from "@prisma/client";
import { FC, ReactNode } from "react";
import { FC, ReactNode, useEffect } from "react";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Icon, Badge } from "@calcom/ui";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { UserAvatars } from "@components/booking/UserAvatars";
import EventTypeDescriptionSafeHTML from "@components/eventtype/EventTypeDescriptionSafeHTML";
@@ -40,6 +42,18 @@ interface Props {
const BookingDescription: FC<Props> = (props) => {
const { profile, eventType, isBookingPage = false, children } = props;
const { t } = useLocale();
const { duration, setQuery: setDuration } = useRouterQuery("duration");
useEffect(() => {
if (eventType.metadata?.multipleDuration !== undefined) {
if (!duration) {
setDuration(eventType.length);
} else {
if (!eventType.metadata?.multipleDuration.includes(Number(duration))) {
setDuration(eventType.length);
}
}
}
}, []);
return (
<>
<UserAvatars
@@ -55,7 +69,7 @@ const BookingDescription: FC<Props> = (props) => {
<h1 className="font-cal dark:text-darkgray-900 mb-6 break-words text-2xl font-semibold text-gray-900">
{eventType.title}
</h1>
<div className="dark:text-darkgray-600 flex flex-col space-y-3 text-sm font-medium text-gray-600">
<div className="dark:text-darkgray-600 flex flex-col space-y-4 text-sm font-medium text-gray-600">
{eventType?.description && (
<div
className={classNames(
@@ -90,14 +104,46 @@ const BookingDescription: FC<Props> = (props) => {
<AvailableEventLocations
locations={eventType.locations as AvailabilityPageProps["eventType"]["locations"]}
/>
<p
<div
className={classNames(
"text-sm font-medium",
"flex flex-nowrap text-sm font-medium",
isBookingPage && "dark:text-darkgray-600 text-gray-600"
)}>
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
</p>
<Icon.FiClock
className={classNames(
"min-h-4 min-w-4 mr-[10px] ml-[2px] inline-block",
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"
size="lg"
className={classNames(
duration === dur.toString()
? "bg-darkgray-200 text-darkgray-900 dark:bg-darkmodebrand dark:!text-darkmodebrandcontrast"
: "hover:bg-darkgray-200 dark:hover:bg-darkmodebrand hover:text-darkgray-900 dark:hover:text-darkmodebrandcontrast dark:bg-darkgray-200 bg-gray-200 text-gray-900 dark:text-white",
"cursor-pointer"
)}
onClick={() => {
setDuration(dur);
}}>
{dur} {t("minute_timeUnit")}
</Badge>
</li>
))}
</ul>
) : (
`${duration} ${t("minutes")}`
)
) : (
`${eventType.length} ${t("minutes")}`
)}
</div>
{children}
</div>
</>
+65 -75
View File
@@ -1,6 +1,6 @@
import { BookingStatus } from "@prisma/client";
import { useRouter } from "next/router";
import { useState, useMemo } from "react";
import { useState } from "react";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
@@ -9,14 +9,20 @@ import { formatTime } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { RouterInputs, RouterOutputs, trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { TextArea } from "@calcom/ui/form/fields";
import MeetingTimeInTimezones from "@calcom/ui/v2/core/MeetingTimeInTimezones";
import Tooltip from "@calcom/ui/v2/core/Tooltip";
import showToast from "@calcom/ui/v2/core/notifications";
import {
Badge,
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
Icon,
MeetingTimeInTimezones,
showToast,
TextArea,
Tooltip,
} from "@calcom/ui";
import useMeQuery from "@lib/hooks/useMeQuery";
@@ -43,9 +49,13 @@ function BookingListItem(booking: BookingItemProps) {
const [rejectionReason, setRejectionReason] = useState<string>("");
const [rejectionDialogIsOpen, setRejectionDialogIsOpen] = useState(false);
const mutation = trpc.viewer.bookings.confirm.useMutation({
onSuccess: () => {
setRejectionDialogIsOpen(false);
showToast(t("booking_confirmation_success"), "success");
onSuccess: (data) => {
if (data.status === BookingStatus.REJECTED) {
setRejectionDialogIsOpen(false);
showToast(t("booking_rejection_success"), "success");
} else {
showToast(t("booking_confirmation_success"), "success");
}
utils.viewer.bookings.invalidate();
},
onError: () => {
@@ -108,7 +118,9 @@ function BookingListItem(booking: BookingItemProps) {
label: isTabRecurring && isRecurring ? t("cancel_all_remaining") : t("cancel"),
/* When cancelling we need to let the UI and the API know if the intention is to
cancel all remaining bookings or just that booking instance. */
href: `/cancel/${booking.uid}${isTabRecurring && isRecurring ? "?allRemainingBookings=true" : ""}`,
href: `/success?uid=${booking.uid}&cancel=true${
isTabRecurring && isRecurring ? "&allRemainingBookings=true" : ""
}`,
icon: Icon.FiX,
},
{
@@ -181,26 +193,14 @@ function BookingListItem(booking: BookingItemProps) {
.concat(booking.recurringInfo?.bookings[BookingStatus.PENDING])
.sort((date1: Date, date2: Date) => date1.getTime() - date2.getTime());
const location = booking.location || "";
const onClickTableData = () => {
router.push({
pathname: "/success",
query: {
date: booking.startTime,
// TODO: Booking when fetched should have id 0 already(for Dynamic Events).
type: booking.eventType.id || 0,
eventSlug: booking.eventType.slug,
username: user?.username || "",
name: booking.attendees[0] ? booking.attendees[0].name : undefined,
email: booking.attendees[0] ? booking.attendees[0].email : undefined,
location: location,
eventName: booking.eventType.eventName || "",
bookingId: booking.id,
recur: booking.recurringEventId,
reschedule: isConfirmed,
uid: booking.uid,
allRemainingBookings: isTabRecurring,
listingStatus: booking.listingStatus,
status: booking.status,
email: booking.attendees[0] ? booking.attendees[0].email : undefined,
},
});
};
@@ -237,9 +237,7 @@ function BookingListItem(booking: BookingItemProps) {
/>
<DialogFooter>
<DialogClose>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<DialogClose />
<Button
disabled={mutation.isLoading}
@@ -467,8 +465,9 @@ const FirstAttendee = ({
user: UserProps;
currentEmail: string | null | undefined;
}) => {
const { t } = useLocale();
return user.email === currentEmail ? (
<div className="inline-block">You</div>
<div className="inline-block">{t("you")}</div>
) : (
<a
key={user.email}
@@ -480,19 +479,19 @@ const FirstAttendee = ({
);
};
const Attendee: React.FC<{ email: string; children: React.ReactNode }> = ({ email, children }) => {
type AttendeeProps = {
name?: string;
email: string;
};
const Attendee = ({ email, name }: AttendeeProps) => {
return (
<a className=" hover:text-blue-500" href={"mailto:" + email} onClick={(e) => e.stopPropagation()}>
{children}
<a className="hover:text-blue-500" href={"mailto:" + email} onClick={(e) => e.stopPropagation()}>
{name || email}
</a>
);
};
interface AttendeeProps {
name: string;
email: string;
}
const DisplayAttendees = ({
attendees,
user,
@@ -500,42 +499,33 @@ const DisplayAttendees = ({
}: {
attendees: AttendeeProps[];
user: UserProps | null;
currentEmail: string | null | undefined;
currentEmail?: string | null;
}) => {
if (attendees.length === 1) {
return (
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
<span>&nbsp;and&nbsp;</span>
<Attendee email={attendees[0].email}>{attendees[0].name}</Attendee>
</div>
);
} else if (attendees.length === 2) {
return (
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
<span>,&nbsp;</span>
<Attendee email={attendees[0].email}>{attendees[0].name}</Attendee>
<div className="inline-block text-sm text-gray-900">&nbsp;and&nbsp;</div>
<Attendee email={attendees[1].email}>{attendees[1].name}</Attendee>
</div>
);
} else {
return (
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
<span>,&nbsp;</span>
<Attendee email={attendees[0].email}>{attendees[0].name}</Attendee>
<span>&nbsp;&&nbsp;</span>
<Tooltip
content={attendees.slice(1).map((attendee, key) => (
<p key={key}>{attendee.name}</p>
))}>
<div className="inline-block">{attendees.length - 1} more</div>
</Tooltip>
</div>
);
}
const { t } = useLocale();
return (
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
{attendees.length > 1 ? <span>,&nbsp;</span> : <span>&nbsp;{t("and")}&nbsp;</span>}
<Attendee {...attendees[0]} />
{attendees.length > 1 && (
<>
<div className="inline-block text-sm text-gray-900">&nbsp;{t("and")}&nbsp;</div>
{attendees.length > 2 ? (
<Tooltip
content={attendees.slice(1).map((attendee) => (
<p key={attendee.email}>
<Attendee {...attendee} />
</p>
))}>
<div className="inline-block">{t("plus_more", { count: attendees.length - 1 })}</div>
</Tooltip>
) : (
<Attendee {...attendees[1]} />
)}
</>
)}
</div>
);
};
const Tag = ({ children, className = "" }: React.PropsWithChildren<{ className?: string }>) => {
+16 -26
View File
@@ -5,8 +5,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Button } from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import { Button, Icon, TextArea } from "@calcom/ui";
type Props = {
booking: {
@@ -22,13 +21,14 @@ type Props = {
team?: string | null;
setIsCancellationMode: (value: boolean) => void;
theme: string | null;
allRemainingBookings: boolean;
};
export default function CancelBooking(props: Props) {
const [cancellationReason, setCancellationReason] = useState<string>("");
const { t } = useLocale();
const router = useRouter();
const { booking, profile, team } = props;
const { booking, profile, team, allRemainingBookings } = props;
const [loading, setLoading] = useState(false);
const telemetry = useTelemetry();
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
@@ -37,7 +37,7 @@ export default function CancelBooking(props: Props) {
return (
<>
{error && (
<div>
<div className="mt-8">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" />
</div>
@@ -51,30 +51,23 @@ export default function CancelBooking(props: Props) {
{!error && (
<div className="mt-5 sm:mt-6">
<label className="text-bookingdark font-medium dark:text-white">{t("cancellation_reason")}</label>
<textarea
<TextArea
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-3 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:mb-3 "
className="mt-2 mb-4 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white "
rows={3}
/>
<div className="flex flex-col-reverse rtl:space-x-reverse sm:flex-row">
{!props.recurringEvent && (
<div className="border-bookinglightest mt-5 flex w-full justify-center border-t pt-3 sm:mt-0 sm:justify-start sm:border-0 sm:pt-0">
<Button
color="secondary"
className="border-0 sm:border"
onClick={() => router.push(`/reschedule/${booking?.uid}`)}>
{t("reschedule_this")}
</Button>
</div>
)}
<div className="mb-2 flex w-full space-x-2 text-right sm:mb-0 ">
<Button color="secondary" onClick={() => props.setIsCancellationMode(false)}>
<div className="flex flex-col-reverse rtl:space-x-reverse ">
<div className="ml-auto flex w-full space-x-4 ">
<Button
className="ml-auto"
color="secondary"
onClick={() => props.setIsCancellationMode(false)}>
{t("nevermind")}
</Button>
<Button
className="flex grow justify-center"
className="flex justify-center"
data-testid="cancel"
onClick={async () => {
setLoading(true);
@@ -82,6 +75,7 @@ export default function CancelBooking(props: Props) {
const payload = {
id: booking?.id,
cancellationReason: cancellationReason,
allRemainingBookings,
};
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());
@@ -95,11 +89,7 @@ export default function CancelBooking(props: Props) {
});
if (res.status >= 200 && res.status < 300) {
await router.push(
`/cancel/success?name=${props.profile.name}&title=${booking?.title}&eventPage=${
profile.slug
}&team=${team ? 1 : 0}&recurring=${!!props.recurringEvent}`
);
await router.replace(router.asPath);
} else {
setLoading(false);
setError(
@@ -110,7 +100,7 @@ export default function CancelBooking(props: Props) {
}
}}
loading={loading}>
{t("cancel_event")}
{props.allRemainingBookings ? t("cancel_all_remaining") : t("cancel_event")}
</Button>
</div>
</div>
@@ -1,6 +1,6 @@
import React from "react";
import { SkeletonText } from "@calcom/ui/v2";
import { SkeletonText } from "@calcom/ui";
function SkeletonLoader() {
return (
+1 -1
View File
@@ -1,7 +1,7 @@
import { FC, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import TimezoneSelect, { ITimezoneOption } from "@calcom/ui/v2/core/TimezoneSelect";
import { ITimezoneOption, TimezoneSelect } from "@calcom/ui";
import { timeZone } from "../../lib/clock";
+14 -3
View File
@@ -7,7 +7,7 @@ export const UserAvatars = ({
users,
...props
}: {
profile: { image: string | null; name?: string | null };
profile: { image: string | null; name?: string | null; username?: string | null };
showMembers: boolean;
users: { username: string | null; name?: string | null }[];
} & Pick<AvatarGroupProps, "size" | "truncateAfter">) => {
@@ -17,15 +17,26 @@ export const UserAvatars = ({
border="border-2 dark:border-darkgray-100 border-white"
items={
[
{ image: profile.image, alt: profile.name, title: profile.name },
{
image: profile.image,
alt: profile.name,
title: profile.name,
href: profile.username ? `${CAL_URL}/${profile.username}` : undefined,
},
...(showMembers
? users.map((user) => ({
title: user.name,
image: `${CAL_URL}/${user.username}/avatar.png`,
alt: user.name || undefined,
href: user.username ? `${CAL_URL}/${user.username}` : undefined,
}))
: []),
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
].filter((item) => !!item.image) as {
image: string;
alt?: string;
title?: string;
href?: string;
}[]
}
size={props.size}
truncateAfter={props.truncateAfter}
@@ -27,13 +27,14 @@ import notEmpty from "@calcom/lib/notEmpty";
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import DatePicker from "@calcom/ui/v2/modules/booker/DatePicker";
import { Icon, DatePicker } from "@calcom/ui";
import { timeZone as localStorageTimeZone } from "@lib/clock";
// import { timeZone } from "@lib/clock";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import Gates, { Gate, GateState } from "@components/Gates";
@@ -71,6 +72,7 @@ const useSlots = ({
endTime,
usernameList,
timeZone,
duration,
}: {
eventTypeId: number;
eventTypeSlug: string;
@@ -78,6 +80,7 @@ const useSlots = ({
endTime?: Dayjs;
usernameList: string[];
timeZone?: string;
duration?: string;
}) => {
const { data, isLoading, isPaused } = trpc.viewer.public.slots.getSchedule.useQuery(
{
@@ -87,6 +90,7 @@ const useSlots = ({
startTime: startTime?.toISOString() || "",
endTime: endTime?.toISOString() || "",
timeZone,
duration,
},
{
enabled: !!startTime && !!endTime,
@@ -127,6 +131,7 @@ const SlotPicker = ({
}) => {
const [selectedDate, setSelectedDate] = useState<Dayjs>();
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
const { duration } = useRouterQuery("duration");
const { date, setQuery: setDate } = useRouterQuery("date");
const { month, setQuery: setMonth } = useRouterQuery("month");
const router = useRouter();
@@ -152,7 +157,7 @@ const SlotPicker = ({
setSelectedDate(dayjs.tz(date, timeZone));
}
}
}, [router.isReady, month, date, timeZone]);
}, [router.isReady, month, date, duration, timeZone]);
const { i18n, isLocaleReady } = useLocale();
const { slots: _1 } = useSlots({
@@ -162,6 +167,7 @@ const SlotPicker = ({
startTime: selectedDate?.startOf("day"),
endTime: selectedDate?.endOf("day"),
timeZone,
duration,
});
const { slots: _2, isLoading } = useSlots({
eventTypeId: eventType.id,
@@ -170,6 +176,7 @@ const SlotPicker = ({
startTime: browsingDate?.startOf("month"),
endTime: browsingDate?.endOf("month"),
timeZone,
duration,
});
const slots = useMemo(() => ({ ..._2, ..._1 }), [_1, _2]);
@@ -231,13 +238,13 @@ function TimezoneDropdown({
return (
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 inline-block rounded-md px-2 py-2 text-left text-gray-600">
<p className="text-sm font-medium">
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-[2px] inline-block h-4 w-4" />
<p className="flex items-center text-sm font-medium">
<Icon.FiGlobe className="min-h-4 min-w-4 mr-[10px] ml-[2px] -mt-[2px] inline-block" />
{timeZone}
{isTimeOptionsOpen ? (
<Icon.FiChevronUp className="ml-1 inline-block h-4 w-4" />
<Icon.FiChevronUp className="min-h-4 min-w-4 ml-1 inline-block" />
) : (
<Icon.FiChevronDown className="ml-1 inline-block h-4 w-4" />
<Icon.FiChevronDown className="min-h-4 min-w-4 ml-1 inline-block" />
)}
</p>
</Popover.Trigger>
@@ -259,23 +266,6 @@ const dateQuerySchema = z.object({
timeZone: z.string().optional().default(""),
});
const useRouterQuery = <T extends string>(name: T) => {
const router = useRouter();
const existingQueryParams = router.asPath.split("?")[1];
const urlParams = new URLSearchParams(existingQueryParams);
const query = Object.fromEntries(urlParams);
const setQuery = (newValue: string | number | null | undefined) => {
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
router.replace({ query: { ...router.query, ...query, [name]: newValue } }, undefined, { shallow: true });
};
return { [name]: query[name], setQuery } as {
[K in T]: string | undefined;
} & { setQuery: typeof setQuery };
};
export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
@@ -5,7 +5,7 @@ import { isValidPhoneNumber } from "libphonenumber-js";
import { useSession } from "next-auth/react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState, useReducer } from "react";
import { useEffect, useMemo, useReducer, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { FormattedNumber, IntlProvider } from "react-intl";
import { ReactMultiEmail } from "react-multi-email";
@@ -14,11 +14,11 @@ import { z } from "zod";
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
import {
locationKeyToString,
getEventLocationValue,
getEventLocationType,
EventLocationType,
getEventLocationType,
getEventLocationValue,
getHumanReadableLocationValue,
locationKeyToString,
} from "@calcom/app-store/locations";
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
import { getEventTypeAppData } from "@calcom/app-store/utils";
@@ -37,17 +37,13 @@ import useTheme from "@calcom/lib/hooks/useTheme";
import { HttpError } from "@calcom/lib/http-error";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Icon } from "@calcom/ui/Icon";
import { Tooltip } from "@calcom/ui/Tooltip";
import { Button } from "@calcom/ui/components";
import AddressInput from "@calcom/ui/form/AddressInputLazy";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { EmailInput, Form } from "@calcom/ui/form/fields";
import { AddressInput, Button, EmailInput, Form, Icon, PhoneInput, Tooltip } from "@calcom/ui";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
import useMeQuery from "@lib/hooks/useMeQuery";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import createBooking from "@lib/mutations/bookings/create-booking";
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
import { parseDate, parseRecurringDates } from "@lib/parseDate";
@@ -90,9 +86,7 @@ const BookingPage = ({
...restProps
}: BookingPageProps) => {
const { t, i18n } = useLocale();
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const user = query.data;
const { duration: queryDuration } = useRouterQuery("duration");
const isEmbed = useIsEmbed(restProps.isEmbed);
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
@@ -109,6 +103,12 @@ const BookingPage = ({
);
const stripeAppData = getStripeAppData(eventType);
// Define duration now that we support multiple duration eventTypes
let duration = eventType.length;
if (queryDuration && !isNaN(Number(queryDuration))) {
duration = Number(queryDuration);
}
useEffect(() => {
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
@@ -122,7 +122,7 @@ const BookingPage = ({
const mutation = useMutation(createBooking, {
onSuccess: async (responseData) => {
const { id, paymentUid } = responseData;
const { uid, paymentUid } = responseData;
if (paymentUid) {
return await router.push(
createPaymentLink({
@@ -138,17 +138,10 @@ const BookingPage = ({
return router.push({
pathname: "/success",
query: {
date,
type: eventType.id,
eventSlug: eventType.slug,
username: profile.slug,
reschedule: !!rescheduleUid,
name: bookingForm.getValues("name"),
email: bookingForm.getValues("email"),
location: responseData.location,
eventName: profile.eventName || "",
bookingId: id,
uid,
isSuccessBookingPage: true,
email: bookingForm.getValues("email"),
eventTypeSlug: eventType.slug,
},
});
},
@@ -156,31 +149,15 @@ const BookingPage = ({
const recurringMutation = useMutation(createRecurringBooking, {
onSuccess: async (responseData = []) => {
const { attendees = [], id, recurringEventId } = responseData[0] || {};
const location = (function humanReadableLocation(location) {
if (!location) {
return;
}
if (location.includes("integration")) {
return t("web_conferencing_details_to_follow");
}
return location;
})(responseData[0].location);
const { uid } = responseData[0] || {};
return router.push({
pathname: "/success",
query: {
date,
type: eventType.id,
eventSlug: eventType.slug,
recur: recurringEventId,
username: profile.slug,
reschedule: !!rescheduleUid,
name: attendees[0].name,
email: attendees[0].email,
location,
eventName: profile.eventName || "",
bookingId: id,
uid,
allRemainingBookings: true,
email: bookingForm.getValues("email"),
eventTypeSlug: eventType.slug,
},
});
},
@@ -346,7 +323,7 @@ const BookingPage = ({
const recurringBookings = recurringDates.map((recurringDate) => ({
...booking,
start: dayjs(recurringDate).format(),
end: dayjs(recurringDate).add(eventType.length, "minute").format(),
end: dayjs(recurringDate).add(duration, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
recurringEventId,
@@ -379,7 +356,7 @@ const BookingPage = ({
mutation.mutate({
...booking,
start: dayjs(date).format(),
end: dayjs(date).add(eventType.length, "minute").format(),
end: dayjs(date).add(duration, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
timeZone: timeZone(),
@@ -515,7 +492,7 @@ const BookingPage = ({
<p key={key}>{timeFormatted}</p>
))}>
<p className="dark:text-darkgray-600 text-sm">
{t("plus_more", { count: recurringStrings.length - 5 })}
+ {t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
@@ -650,7 +627,7 @@ const BookingPage = ({
)}
</>
{/* TODO: Change name and id ="phone" to something generic */}
{AttendeeInput && (
{AttendeeInput && !disableInput && (
<div className="mb-4">
<label
htmlFor={
@@ -687,7 +664,6 @@ const BookingPage = ({
: ""
}
required
disabled={disableInput}
/>
</div>
{bookingForm.formState.errors.phone && (
@@ -868,7 +844,7 @@ const BookingPage = ({
) : (
<textarea
{...bookingForm.register("notes")}
required={!!eventType.metadata.additionalNotesRequired}
required={!!eventType.metadata?.additionalNotesRequired}
id="notes"
name="notes"
rows={3}
@@ -883,7 +859,7 @@ const BookingPage = ({
{!eventType.disableGuests && !guestToggle && (
<Button
type="button"
color="minimalSecondary"
color="minimal"
size="icon"
tooltip={t("additional_guests")}
StartIcon={Icon.FiUserPlus}
@@ -901,7 +877,6 @@ const BookingPage = ({
</Button>
<Button
type="submit"
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast mr-auto dark:border-transparent"
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}
loading={mutation.isLoading || recurringMutation.isLoading}>
{rescheduleUid ? t("reschedule") : t("confirm")}
@@ -934,7 +909,7 @@ function ErrorMessage({ error }: { error: unknown }) {
<div className="ltr:ml-3 rtl:mr-3">
<p className="text-sm text-yellow-700">
{rescheduleUid ? t("reschedule_fail") : t("booking_fail")}{" "}
{error instanceof HttpError || error instanceof Error ? error.message : "Unknown error"}
{error instanceof HttpError || error instanceof Error ? t(error.message) : "Unknown error"}
</p>
</div>
</div>
@@ -1,74 +0,0 @@
import * as DialogPrimitive from "@radix-ui/react-dialog";
import React, { PropsWithChildren } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/Button";
import { DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
export type DeleteStripeDialogContentProps = {
cancelAllBookingsBtnText?: string;
removeBtnText?: string;
cancelBtnText?: string;
onConfirm?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onRemove?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
title: string;
variety?: "danger" | "warning" | "success";
};
export default function DeleteStripeDialogContent(props: PropsWithChildren<DeleteStripeDialogContentProps>) {
const { t } = useLocale();
const {
title,
variety,
cancelAllBookingsBtnText,
removeBtnText,
cancelBtnText = t("cancel"),
onConfirm,
onRemove,
children,
} = props;
return (
<DialogContent>
<div className="flex">
{variety && (
<div className="mt-0.5 ltr:mr-3">
{variety === "danger" && (
<div className="mx-auto rounded-full bg-red-100 p-2 text-center">
<Icon.FiAlertTriangle className="h-5 w-5 text-red-600" />
</div>
)}
{variety === "warning" && (
<div className="mx-auto rounded-full bg-orange-100 p-2 text-center">
<Icon.FiAlertTriangle className="h-5 w-5 text-orange-600" />
</div>
)}
{variety === "success" && (
<div className="mx-auto rounded-full bg-green-100 p-2 text-center">
<Icon.FiCheck className="h-5 w-5 text-green-600" />
</div>
)}
</div>
)}
<div>
<DialogPrimitive.Title className="font-cal text-xl text-gray-900">{title}</DialogPrimitive.Title>
<DialogPrimitive.Description className="text-sm text-neutral-500">
{children}
</DialogPrimitive.Description>
</div>
</div>
<div className="mt-5 flex flex-row-reverse gap-x-2 sm:mt-8">
<DialogClose onClick={onConfirm} asChild>
<Button color="alert">{cancelAllBookingsBtnText}</Button>
</DialogClose>
<DialogClose onClick={onRemove} asChild>
<Button color="alert2">{removeBtnText}</Button>
</DialogClose>
<DialogClose asChild>
<Button color="secondary">{cancelBtnText}</Button>
</DialogClose>
</div>
</DialogContent>
);
}
+156 -122
View File
@@ -3,23 +3,21 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { components } from "react-select";
import { z } from "zod";
import {
LocationType,
getEventLocationType,
EventLocationType,
getEventLocationType,
getHumanReadableLocationValue,
getMessageForOrganizer,
LocationObject,
LocationType,
} from "@calcom/app-store/locations";
import { getMessageForOrganizer } from "@calcom/app-store/locations";
import { getHumanReadableLocationValue } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { Form } from "@calcom/ui/form/fields";
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Icon, PhoneInput } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
@@ -42,6 +40,7 @@ interface ISetLocationDialog {
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean;
setSelectedLocation?: (param: OptionTypeBase | undefined) => void;
setEditingLocationType?: (param: string) => void;
}
const LocationInput = (props: {
@@ -79,6 +78,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
isOpenDialog,
defaultValues,
setSelectedLocation,
setEditingLocationType,
} = props;
const { t } = useLocale();
const locationsQuery = trpc.viewer.locationOptions.useQuery();
@@ -212,13 +212,13 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
return (
<Dialog open={isOpenDialog}>
<DialogContent asChild>
<div className="inline-block transform rounded-sm bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<DialogContent>
<div className="flex flex-row space-x-3">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
</div>
<div className="w-full">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{t("edit_location")}
</h3>
@@ -226,118 +226,152 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
<p className="text-sm text-gray-400">{t("this_input_will_shown_booking_this_event")}</p>
)}
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" />
</div>
{booking && (
<>
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
<p className="mb-2 ml-1 text-sm text-black">
{getHumanReadableLocationValue(booking.location, t)}
</p>
</>
)}
<Form
form={locationFormMethods}
handleSubmit={async (values) => {
const { locationType: newLocation, displayLocationPublicly } = values;
<div className="mt-3 text-center sm:mt-0 sm:text-left" />
let details = {};
if (newLocation === LocationType.InPerson) {
details = {
address: values.locationAddress,
};
}
const eventLocationType = getEventLocationType(newLocation);
{booking && (
<>
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
<p className="mb-2 ml-1 text-sm text-black">
{getHumanReadableLocationValue(booking.location, t)}
</p>
</>
)}
<Form
form={locationFormMethods}
handleSubmit={async (values) => {
const { locationType: newLocation, displayLocationPublicly } = values;
// TODO: There can be a property that tells if it is to be saved in `link`
if (
newLocation === LocationType.Link ||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
) {
details = { link: values.locationLink };
}
let details = {};
if (newLocation === LocationType.InPerson) {
details = {
address: values.locationAddress,
};
}
const eventLocationType = getEventLocationType(newLocation);
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
// TODO: There can be a property that tells if it is to be saved in `link`
if (
newLocation === LocationType.Link ||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
) {
details = { link: values.locationLink };
}
if (eventLocationType?.organizerInputType) {
details = {
...details,
displayLocationPublicly,
};
}
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister([
"locationType",
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
}}>
<QueryCell
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select
maxMenuHeight={150}
name="location"
defaultValue={selection}
options={
booking
? locationOptions.filter(
(location) =>
location.value !== "phone" && location.value !== "attendeeInPerson"
)
: locationOptions
}
isSearchable
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)}
/>
);
}}
/>
{selectedLocation && LocationOptions}
<div className="mt-4 flex justify-end space-x-2">
<Button
onClick={() => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister("locationType");
if (eventLocationType?.organizerInputType) {
details = {
...details,
displayLocationPublicly,
};
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister([
"locationType",
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
}}>
<QueryCell
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
if (booking) {
locationOptions.forEach((location) => {
if (location.label === "phone") {
location.options.filter((l) => l.value !== "phone");
} else if (location.label === "in person") {
location.options.filter((l) => l.value !== "attendeeInPerson");
}
});
}
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select<{ label: string; value: string; icon?: string }>
maxMenuHeight={150}
name="location"
defaultValue={selection}
options={locationOptions}
components={{
Option: (props) => (
<components.Option {...props}>
<div className="flex items-center gap-3">
{props.data.icon && (
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
)}
<span
className={classNames(
"text-sm font-medium",
props.isSelected ? "text-white" : "text-gray-900"
)}>
{props.data.label}
</span>
</div>
</components.Option>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-3">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => (
<p className="text-xs font-medium text-gray-600">{e.label}</p>
)}
isSearchable
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)}
/>
);
}}
type="button"
color="secondary">
{t("cancel")}
</Button>
<Button type="submit">{t("update")}</Button>
</div>
</Form>
/>
{selectedLocation && LocationOptions}
<DialogFooter>
<div className="mt-4 flex justify-end space-x-2">
<Button
onClick={() => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
setEditingLocationType?.("");
locationFormMethods.unregister("locationType");
}}
type="button"
color="secondary">
{t("cancel")}
</Button>
<Button type="submit">{t("update")}</Button>
</div>
</DialogFooter>
</Form>
</div>
</div>
</DialogContent>
</Dialog>
+13 -10
View File
@@ -1,12 +1,18 @@
import React, { useState, Dispatch, SetStateAction } from "react";
import { Dispatch, SetStateAction, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components/button";
import { TextArea } from "@calcom/ui/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
Icon,
showToast,
TextArea,
} from "@calcom/ui";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -41,7 +47,6 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
</div>
<div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} />
<p className="-mt-8 text-sm text-gray-500">{t("reschedule_modal_description")}</p>
<p className="mt-6 mb-2 text-sm font-bold text-black">
{t("reason_for_reschedule_request")}
@@ -56,9 +61,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
/>
<DialogFooter>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<DialogClose />
<Button
data-testid="send_request"
disabled={isLoading}
@@ -1,6 +1,6 @@
import { FormValues } from "pages/event-types/[type]";
import { Controller, useFormContext } from "react-hook-form";
import { SingleValueProps, OptionProps, components } from "react-select";
import { components, OptionProps, SingleValueProps } from "react-select";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
@@ -8,12 +8,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Icon } from "@calcom/ui";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { SettingsToggle } from "@calcom/ui/v2";
import Select from "@calcom/ui/v2/core/form/select";
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
import { Badge, Button, Icon, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
@@ -11,19 +11,25 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/Alert";
import { Button } from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import Dropdown, {
import {
Alert,
Button,
Dialog,
DialogClose,
DialogContent,
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@calcom/ui/Dropdown";
import { Icon } from "@calcom/ui/Icon";
import { Form, InputLeading, TextAreaField, TextField } from "@calcom/ui/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
Form,
Icon,
InputLeading,
showToast,
TextAreaField,
TextField,
} from "@calcom/ui";
import { HttpError } from "@lib/core/http/error";
import { slugify } from "@lib/slugify";
@@ -258,9 +264,7 @@ export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
<Button type="submit" loading={createMutation.isLoading}>
{t("continue")}
</Button>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<DialogClose />
</div>
</Form>
</DialogContent>
@@ -1,10 +1,8 @@
import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
import React, { FC } from "react";
import { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import { Button } from "@calcom/ui/components";
import { TextField } from "@calcom/ui/components/form";
import { Select } from "@calcom/ui/v2";
import { Button, Select, TextField } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
@@ -4,24 +4,21 @@ import { isValidPhoneNumber } from "libphonenumber-js";
import { Trans } from "next-i18next";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { components } from "react-select";
import { z } from "zod";
import {
LocationType,
getEventLocationType,
EventLocationType,
getEventLocationType,
getHumanReadableLocationValue,
getMessageForOrganizer,
LocationObject,
LocationType,
} from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { Label, Form } from "@calcom/ui/components/form";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { Dialog, DialogContent } from "@calcom/ui/v2";
import { Select } from "@calcom/ui/v2/";
import { Button, Dialog, DialogContent, Form, Icon, Label, PhoneInput, Select } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
@@ -214,7 +211,6 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
<DialogContent
type="creation"
Icon={Icon.FiMapPin}
useOwnActionButtons
title={t("edit_location")}
description={!booking ? t("this_input_will_shown_booking_this_event") : undefined}>
{booking && (
@@ -278,20 +274,48 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
if (booking) {
locationOptions.forEach((location) => {
if (location.label === "phone") {
location.options.filter((l) => l.value !== "phone");
}
});
}
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select
<Select<{ label: string; value: string; icon?: string }>
maxMenuHeight={150}
name="location"
defaultValue={selection}
options={
booking
? locationOptions.filter((location) => location.value !== "phone")
: locationOptions
}
options={locationOptions}
components={{
Option: (props) => (
<components.Option {...props}>
<div className="flex items-center gap-3">
{props.data.icon && (
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
)}
<span
className={classNames(
"text-sm font-medium",
props.isSelected ? "text-white" : "text-gray-900"
)}>
{props.data.label}
</span>
</div>
</components.Option>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-2.5">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => <p className="text-xs font-medium text-gray-600">{e.label}</p>}
isSearchable={false}
onChange={(val) => {
if (val) {
@@ -10,9 +10,21 @@ import DestinationCalendarSelector from "@calcom/features/calendars/DestinationC
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Checkbox, Button, TextField, Label } from "@calcom/ui/components";
import { CustomInputItem, Dialog, DialogContent, SettingsToggle, showToast, Tooltip } from "@calcom/ui/v2";
import {
Button,
Checkbox,
CustomInputItem,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
Icon,
Label,
SettingsToggle,
showToast,
TextField,
Tooltip,
} from "@calcom/ui";
import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm";
@@ -74,6 +86,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
defaultValue={eventType.destinationCalendar || undefined}
render={({ field: { onChange, value } }) => (
<DestinationCalendarSelector
destinationCalendar={eventType.destinationCalendar}
value={value ? value.externalId : undefined}
onChange={onChange}
hidePlaceholder
@@ -97,7 +110,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
StartIcon={Icon.FiEdit}
size="icon"
color="minimal"
className="hover:stroke-3 hover:bg-transparent hover:text-black"
className="hover:stroke-3 min-w-fit px-0 hover:bg-transparent hover:text-black"
onClick={() => setShowEventNameTip((old) => !old)}
/>
}
@@ -341,11 +354,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<DialogContent
title={t("custom_event_name")}
description={t("custom_event_name_description")}
type="creation"
actionText="Create"
// Set event name back to what it was on close
actionOnClose={() => formMethods.setValue("eventName", eventType.eventName ?? "")}
actionOnClick={() => setShowEventNameTip(false)}>
type="creation">
<TextField
label={t("event_name")}
type="text"
@@ -359,6 +368,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<p>{`{HOST/ATTENDEE} = ${t("dynamically_display_attendee_or_organizer")}`}</p>
<p>{`{LOCATION} = ${t("event_location")}`}</p>
</div>
<DialogFooter>
<Button color="primary" onClick={() => setShowEventNameTip(false)}>
{t("create")}
</Button>
<DialogClose onClick={() => formMethods.setValue("eventName", eventType.eventName ?? "")} />
</DialogFooter>
</DialogContent>
</Dialog>
)}
@@ -372,7 +387,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
type="creation"
Icon={Icon.FiPlus}
title={t("add_new_custom_input_field")}
useOwnActionButtons
description={t("this_input_will_shown_booking_this_event")}>
<CustomInputTypeForm
selectedCustomInput={selectedCustomInput}
@@ -1,5 +1,4 @@
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import React from "react";
import { useFormContext } from "react-hook-form";
import EventTypeAppContext, { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
@@ -8,10 +7,7 @@ import { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
import { EventTypeAppsList } from "@calcom/app-store/utils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
import { Button } from "@calcom/ui/components";
import { EmptyScreen } from "@calcom/ui/v2";
import { Button, EmptyScreen, ErrorBoundary, Icon } from "@calcom/ui";
type EventType = Pick<EventTypeSetupInfered, "eventType">["eventType"] &
EventTypeAppCardComponentProps["eventType"];
+112 -18
View File
@@ -1,23 +1,25 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { useFormContext, Controller, useWatch } from "react-hook-form";
import { useMemo, useRef, useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { classNames } from "@calcom/lib";
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PeriodType } from "@calcom/prisma/client";
import type { BookingLimit } from "@calcom/types/Calendar";
import { Icon } from "@calcom/ui";
import { Button } from "@calcom/ui/components";
import { Label, Input, MinutesField } from "@calcom/ui/components/form";
import { Select, SettingsToggle } from "@calcom/ui/v2";
import DateRangePicker from "@calcom/ui/v2/core/form/date-range-picker/DateRangePicker";
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">) => {
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "eventType">) => {
const { t } = useLocale();
const formMethods = useFormContext<FormValues>();
const { eventType } = props;
const minimumBookingNoticeType = useRef<DurationType>(findDurationType(eventType.minimumBookingNotice));
const prevBookingNoticeType = useRef<DurationType>(minimumBookingNoticeType.current);
const minimumBookingNoticeInDurationTypeFormValue = formMethods.watch("minimumBookingNoticeInDurationType");
const PERIOD_TYPES = [
{
type: "ROLLING" as const,
@@ -47,6 +49,57 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
defaultValue: periodType?.type,
});
const onMinimumNoticeDurationTypeChange = useMemo(
() => (durationType?: DurationType) => {
if (typeof durationType === "undefined") return;
// Store current selected type in ref to use in previous run for comparrison.
minimumBookingNoticeType.current = durationType;
// Uses the previous selected type and converts the value to the new type.
// Eg day was selected before, user selects hours now, so we multiple * 24 and round up.
const minimumBookingNoticeInDurationType = convertToNewDurationType(
prevBookingNoticeType.current,
durationType,
Number(minimumBookingNoticeInDurationTypeFormValue)
);
// Uses the round up value in the new type to calculate the value in minutes.
// We should NOT use minimumBookingNoticeInDurationTypeFormValue here, since that
// would break in the case that the user enters 90 minutes, changes the type to
// hours (that will show 2 hours), but the value in minutes would then remain 90,
// which is something else than the user things they save.
const minimumBookingNoticeInMinutes = convertToNewDurationType(
durationType,
"minutes",
Number(minimumBookingNoticeInDurationType)
);
// Updates both form values as well as stores the current type as previous type in ref.
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
formMethods.setValue("minimumBookingNoticeInDurationType", minimumBookingNoticeInDurationType);
prevBookingNoticeType.current = durationType;
},
[formMethods, minimumBookingNoticeInDurationTypeFormValue]
);
/**
* When the user inputs a new value for minimumBookingNoticeInDurationType,
* we calculate the value in minutes, and update this hidden field as well.
*/
const onMinimumNoticeChange = useMemo(
() => (duration: string) => {
const minimumBookingNoticeInMinutes = convertToNewDurationType(
minimumBookingNoticeType.current,
"minutes",
Number(duration)
);
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
},
[formMethods]
);
return (
<div>
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
@@ -116,13 +169,54 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
</div>
</div>
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="w-full">
<MinutesField
required
label={t("minimum_booking_notice")}
type="number"
placeholder="120"
{...formMethods.register("minimumBookingNotice", { valueAsNumber: true })}
<div className="flex w-full items-end justify-end">
<Controller
name="minimumBookingNotice"
control={formMethods.control}
render={() => {
const durationTypeOptions = [
{
label: t("minutes"),
value: "minutes",
},
{
label: t("hours"),
value: "hours",
},
{
label: t("days"),
value: "days",
},
];
return (
<>
<div className="w-1/2 md:w-3/4">
<InputField
required
label={t("minimum_booking_notice")}
type="number"
placeholder="120"
className="mr-2 mb-0 h-[38px] rounded-[4px]"
{...formMethods.register("minimumBookingNoticeInDurationType", {
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
onMinimumNoticeChange(event.target.value),
})}
/>
<input type="hidden" {...formMethods.register("minimumBookingNotice")} />
</div>
<Select
isSearchable={false}
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
defaultValue={durationTypeOptions.find(
(option) => option.value === minimumBookingNoticeType.current
)}
onChange={(input) => onMinimumNoticeDurationTypeChange(input?.value as DurationType)}
options={durationTypeOptions}
/>
</>
);
}}
/>
</div>
<div className="w-full">
@@ -136,7 +230,7 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
label: t("slot_interval_default"),
value: -1,
},
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
@@ -191,7 +285,7 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
<SettingsToggle
title={t("limit_future_bookings")}
description={t("limit_future_bookings_description")}
checked={value !== "UNLIMITED"}
checked={value && value !== "UNLIMITED"}
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
<RadioGroup.Root
defaultValue={watchPeriodType}
+141 -27
View File
@@ -4,19 +4,17 @@ import { isValidPhoneNumber } from "libphonenumber-js";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { MultiValue } from "react-select";
import { z } from "zod";
import { getEventLocationType, EventLocationType } from "@calcom/app-store/locations";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { Label, TextField } from "@calcom/ui/components/form";
import { Select, Skeleton } from "@calcom/ui/v2";
import { Button, Icon, Label, Select, Skeleton, TextField, SettingsToggle } from "@calcom/ui";
import { slugify } from "@lib/slugify";
import { EditLocationDialog } from "@components/eventtype/EditLocationDialog";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
type OptionTypeBase = {
label: string;
@@ -31,7 +29,36 @@ export const EventSetupTab = (
const formMethods = useFormContext<FormValues>();
const { eventType, locationOptions, team } = props;
const [showLocationModal, setShowLocationModal] = useState(false);
const [editingLocationType, setEditingLocationType] = useState<string>("");
const [selectedLocation, setSelectedLocation] = useState<OptionTypeBase | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
const multipleDurationOptions = [
{ value: 5, label: t("multiple_duration_mins", { count: 5 }) },
{ value: 10, label: t("multiple_duration_mins", { count: 10 }) },
{ value: 15, label: t("multiple_duration_mins", { count: 15 }) },
{ value: 20, label: t("multiple_duration_mins", { count: 20 }) },
{ value: 25, label: t("multiple_duration_mins", { count: 25 }) },
{ value: 30, label: t("multiple_duration_mins", { count: 30 }) },
{ value: 45, label: t("multiple_duration_mins", { count: 45 }) },
{ value: 50, label: t("multiple_duration_mins", { count: 50 }) },
{ value: 60, label: t("multiple_duration_mins", { count: 60 }) },
{ value: 75, label: t("multiple_duration_mins", { count: 75 }) },
{ value: 80, label: t("multiple_duration_mins", { count: 80 }) },
{ value: 90, label: t("multiple_duration_mins", { count: 90 }) },
{ value: 120, label: t("multiple_duration_mins", { count: 120 }) },
{ value: 180, label: t("multiple_duration_mins", { count: 180 }) },
];
const [selectedMultipleDuration, setSelectedMultipleDuration] = useState<
MultiValue<{
value: number;
label: string;
}>
>(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value)));
const [defaultDuration, setDefaultDuration] = useState(
selectedMultipleDuration.find((opt) => opt.value === eventType.length) ?? null
);
const openLocationModal = (type: EventLocationType["type"]) => {
setSelectedLocation(locationOptions.find((option) => option.value === type));
@@ -46,14 +73,23 @@ export const EventSetupTab = (
);
};
const addLocation = (newLocationType: EventLocationType["type"], details = {}) => {
const existingIdx = formMethods.getValues("locations").findIndex((loc) => newLocationType === loc.type);
const saveLocation = (newLocationType: EventLocationType["type"], details = {}) => {
const locationType = editingLocationType !== "" ? editingLocationType : newLocationType;
const existingIdx = formMethods.getValues("locations").findIndex((loc) => locationType === loc.type);
if (existingIdx !== -1) {
const copy = formMethods.getValues("locations");
copy[existingIdx] = {
...formMethods.getValues("locations")[existingIdx],
...details,
};
if (editingLocationType !== "") {
copy[existingIdx] = {
...details,
type: newLocationType,
};
} else {
copy[existingIdx] = {
...formMethods.getValues("locations")[existingIdx],
...details,
};
}
formMethods.setValue("locations", copy);
} else {
formMethods.setValue(
@@ -61,6 +97,8 @@ export const EventSetupTab = (
formMethods.getValues("locations").concat({ type: newLocationType, ...details })
);
}
setEditingLocationType("");
setShowLocationModal(false);
};
@@ -118,7 +156,7 @@ export const EventSetupTab = (
if (eventLocationType.organizerInputType) {
openLocationModal(newLocationType);
} else {
addLocation(newLocationType);
saveLocation(newLocationType);
}
}
}}
@@ -138,10 +176,10 @@ export const EventSetupTab = (
<div key={index} className="flex flex-grow items-center">
<img
src={eventLocationType.iconUrl}
className="h-6 w-6"
className="h-4 w-4"
alt={`${eventLocationType.label} logo`}
/>
<span className="truncate text-sm ltr:ml-2 rtl:mr-2">
<span className="truncate text-sm ltr:ml-1 rtl:mr-1">
{location[eventLocationType.defaultValueVariable] || eventLocationType.label}
</span>
</div>
@@ -153,6 +191,7 @@ export const EventSetupTab = (
locationFormMethods.unregister("locationLink");
locationFormMethods.unregister("locationAddress");
locationFormMethods.unregister("locationPhoneNumber");
setEditingLocationType(location.type);
openLocationModal(location.type);
}}
aria-label={t("edit")}
@@ -209,17 +248,91 @@ export const EventSetupTab = (
setValueAs: (v) => slugify(v),
})}
/>
<TextField
required
name="length"
type="number"
label={t("duration")}
addOnSuffix={<>{t("minutes")}</>}
defaultValue={eventType.length ?? 15}
onChange={(e) => {
formMethods.setValue("length", Number(e.target.value));
}}
/>
{multipleDuration ? (
<div className="space-y-4">
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("available_durations")}
</Skeleton>
<Select
isMulti
defaultValue={selectedMultipleDuration}
name="metadata.multipleDuration"
isSearchable={false}
className="h-auto !min-h-[36px] text-sm"
options={multipleDurationOptions}
onChange={(options) => {
const values = options
.map((opt) => opt.value)
.sort(function (a, b) {
return a - b;
});
setMultipleDuration(values);
setSelectedMultipleDuration(options);
if (!options.find((opt) => opt.value === defaultDuration?.value)) {
if (options.length > 0) {
setDefaultDuration(options[0]);
} else {
setDefaultDuration(null);
}
}
if (options.length === 1 && defaultDuration === null) {
setDefaultDuration(options[0]);
}
formMethods.setValue("metadata.multipleDuration", values);
}}
/>
</div>
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("default_duration")}
</Skeleton>
<Select
value={defaultDuration}
isSearchable={false}
name="length"
className="text-sm"
noOptionsMessage={() => t("default_duration_no_options")}
options={selectedMultipleDuration}
onChange={(option) => {
setDefaultDuration(
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
);
formMethods.setValue("length", Number(option?.value));
}}
/>
</div>
</div>
) : (
<TextField
required
name="length"
type="number"
label={t("duration")}
addOnSuffix={<>{t("minutes")}</>}
defaultValue={eventType.length ?? 15}
onChange={(e) => {
formMethods.setValue("length", Number(e.target.value));
}}
/>
)}
<div className="!mt-4 [&_label]:my-1 [&_label]:font-normal">
<SettingsToggle
title={t("allow_booker_to_select_duration")}
checked={multipleDuration !== undefined}
onCheckedChange={() => {
if (multipleDuration !== undefined) {
setMultipleDuration(undefined);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("length", eventType.length);
} else {
setMultipleDuration([]);
formMethods.setValue("metadata.multipleDuration", []);
formMethods.setValue("length", 0);
}
}}
/>
</div>
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("location")}
@@ -237,12 +350,13 @@ export const EventSetupTab = (
<EditLocationDialog
isOpenDialog={showLocationModal}
setShowLocationModal={setShowLocationModal}
saveLocation={addLocation}
saveLocation={saveLocation}
defaultValues={formMethods.getValues("locations")}
selection={
selectedLocation ? { value: selectedLocation.value, label: selectedLocation.label } : undefined
}
setSelectedLocation={setSelectedLocation}
setEditingLocationType={setEditingLocationType}
/>
</div>
);
@@ -5,10 +5,7 @@ import { Controller, useFormContext } from "react-hook-form";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { Button, Avatar } from "@calcom/ui/components";
import { Label } from "@calcom/ui/components/form";
import { Select, CheckedTeamSelect } from "@calcom/ui/v2";
import { Avatar, Button, CheckedTeamSelect, Icon, Label, Select } from "@calcom/ui";
interface IMemberToValue {
id: number | null;
@@ -10,20 +10,27 @@ import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import { Button, ButtonGroup, Label } from "@calcom/ui/components";
import { HorizontalTabs, showToast, Switch, Tooltip, VerticalTabs } from "@calcom/ui/v2";
import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent";
import { Dialog } from "@calcom/ui/v2/core/Dialog";
import Divider from "@calcom/ui/v2/core/Divider";
import Dropdown, {
import {
Button,
ButtonGroup,
ConfirmationDialogContent,
Dialog,
Divider,
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@calcom/ui/v2/core/Dropdown";
import Shell from "@calcom/ui/v2/core/Shell";
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
import { Skeleton } from "@calcom/ui/v2/core/skeleton";
HorizontalTabs,
Icon,
Label,
Shell,
showToast,
Skeleton,
Switch,
Tooltip,
VerticalDivider,
VerticalTabs,
} from "@calcom/ui";
import { ClientSuspense } from "@components/ClientSuspense";
import { EmbedButton, EmbedDialog } from "@components/Embed";
@@ -202,7 +209,6 @@ function EventTypeSingleLayout({
color="secondary"
size="icon"
StartIcon={Icon.FiLink}
combined
tooltip={t("copy_link")}
onClick={() => {
navigator.clipboard.writeText(permalink);
@@ -1 +1 @@
export { default } from "@calcom/features/ee/workflows/components/v2/EventWorkflowsTab";
export { default } from "@calcom/features/ee/workflows/components/EventWorkflowsTab";
@@ -5,8 +5,7 @@ import { useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Frequency } from "@calcom/prisma/zod-utils";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Alert } from "@calcom/ui/Alert";
import { Select, SettingsToggle } from "@calcom/ui/v2";
import { Alert, Select, SettingsToggle } from "@calcom/ui";
type RecurringEventControllerProps = {
recurringEvent: RecurringEvent | null;
@@ -1,5 +1,4 @@
import { Icon } from "@calcom/ui/Icon";
import { SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
import { Icon, SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
function SkeletonLoader() {
return (
@@ -37,7 +36,7 @@ function SkeletonItem() {
</li>
<li className="flex items-center whitespace-nowrap">
<Icon.FiUser className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<SkeletonText className="h-4 w-16" />
<SkeletonText className="h-4 w-12" />
</li>
</ul>
</div>
@@ -1,7 +1,7 @@
import { InstallAppButtonWithoutPlanCheck } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui/components/button";
import { Button } from "@calcom/ui";
interface ICalendarItem {
title: string;
@@ -1,8 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import { trpc } from "@calcom/trpc/react";
import { Switch } from "@calcom/ui/v2";
import showToast from "@calcom/ui/v2/core/notifications";
import { showToast, Switch } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -3,8 +3,7 @@ import { ArrowRightIcon } from "@heroicons/react/solid";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { List } from "@calcom/ui/List";
import { SkeletonAvatar, SkeletonText, SkeletonButton } from "@calcom/ui/v2";
import { SkeletonAvatar, SkeletonText, SkeletonButton, List } from "@calcom/ui";
import { CalendarItem } from "../components/CalendarItem";
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
@@ -7,8 +7,7 @@ import { DEFAULT_SCHEDULE } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc, TRPCClientErrorLike } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button } from "@calcom/ui/components/button";
import { Form } from "@calcom/ui/form/fields";
import { Button, Form } from "@calcom/ui";
interface ISetupAvailabilityProps {
nextStep: () => void;
@@ -6,10 +6,7 @@ import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components";
import { TextArea } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/v2";
import ImageUploader from "@calcom/ui/v2/core/ImageUploader";
import { Button, ImageUploader, showToast, TextArea } from "@calcom/ui";
import { AvatarSSR } from "@components/ui/AvatarSSR";
@@ -1,13 +1,12 @@
import { ArrowRightIcon } from "@heroicons/react/outline";
import { useRef, useState } from "react";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components/button";
import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
import { Button, TimezoneSelect } from "@calcom/ui";
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
@@ -1,293 +0,0 @@
import { useMutation } from "@tanstack/react-query";
import { Fragment } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import { List } from "@calcom/ui/List";
import { ShellSubHeading } from "@calcom/ui/Shell";
import Switch from "@calcom/ui/Switch";
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
import showToast from "@calcom/ui/v2/core/notifications";
import { QueryCell } from "@lib/QueryCell";
import AdditionalCalendarSelector from "@components/AdditionalCalendarSelector";
import DestinationCalendarSelector from "@components/DestinationCalendarSelector";
import DisconnectIntegration from "./DisconnectIntegration";
import IntegrationListItem from "./IntegrationListItem";
import SubHeadingTitleWithConnections from "./SubHeadingTitleWithConnections";
type Props = {
onChanged: () => unknown | Promise<unknown>;
fromOnboarding?: boolean;
};
function CalendarSwitch(props: {
type: string;
externalId: string;
title: string;
defaultSelected: boolean;
}) {
const utils = trpc.useContext();
const mutation = useMutation<
unknown,
unknown,
{
isOn: boolean;
}
>(
async ({ isOn }) => {
const body = {
integration: props.type,
externalId: props.externalId,
};
if (isOn) {
const res = await fetch("/api/availability/calendar", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
} else {
const res = await fetch("/api/availability/calendar", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
}
},
{
async onSettled() {
await utils.viewer.integrations.invalidate();
},
onError() {
showToast(`Something went wrong when toggling "${props.title}""`, "error");
},
}
);
return (
<div className="py-1">
<Switch
key={props.externalId}
name="enabled"
label={props.title}
defaultChecked={props.defaultSelected}
onCheckedChange={(isOn: boolean) => {
mutation.mutate({ isOn });
}}
/>
</div>
);
}
function CalendarList(props: Props) {
const { t } = useLocale();
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
return (
<QueryCell
query={query}
success={({ data }) => (
<List>
{data.items.map((item) => (
<IntegrationListItem
name={item.name}
slug={item.slug}
key={item.title}
title={item.title}
logo={item.logo}
description={item.description}
actions={
<InstallAppButton
type={item.type}
render={(buttonProps) => (
<Button color="secondary" {...buttonProps}>
{t("connect")}
</Button>
)}
onChanged={() => props.onChanged()}
/>
}
/>
))}
</List>
)}
/>
);
}
function ConnectedCalendarsList(props: Props) {
const { t } = useLocale();
const query = trpc.viewer.connectedCalendars.useQuery(undefined, {
suspense: true,
trpc: {},
});
const { fromOnboarding } = props;
return (
<QueryCell
query={query}
empty={() => null}
success={({ data }) => {
if (!data.connectedCalendars.length) {
return null;
}
return (
<List>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars ? (
<IntegrationListItem
slug={item.integration.slug}
title={item.integration.title}
logo={item.integration.logo}
description={item.primary?.externalId || "No external Id"}
actions={
<DisconnectIntegration
id={item.credentialId}
externalId={item.primary?.externalId}
render={(btnProps) => (
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
{t("disconnect")}
</Button>
)}
onOpenChange={props.onChanged}
/>
}>
{!fromOnboarding && (
<>
<p className="px-4 pt-4 text-sm text-neutral-500">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-2 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
type={item.integration.type}
defaultSelected={cal.isSelected}
/>
))}
</ul>
</>
)}
</IntegrationListItem>
) : (
<Alert
severity="warning"
title={t("something_went_wrong")}
message={t("calendar_error")}
actions={
<DisconnectIntegration
id={item.credentialId}
render={(btnProps) => (
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
Disconnect
</Button>
)}
onOpenChange={() => props.onChanged()}
/>
}
/>
)}
</Fragment>
))}
</List>
);
}}
/>
);
}
export function CalendarListContainer(props: {
heading?: boolean;
items?: RouterOutputs["viewer"]["integrations"]["items"];
fromOnboarding?: boolean;
}) {
const { t } = useLocale();
const { heading = true, fromOnboarding } = props;
const utils = trpc.useContext();
const onChanged = () =>
Promise.allSettled([
utils.viewer.integrations.invalidate(
{ variant: "calendar", onlyInstalled: true },
{
exact: true,
}
),
utils.viewer.connectedCalendars.invalidate(),
]);
const query = trpc.viewer.connectedCalendars.useQuery();
const installedCalendars = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
const mutation = trpc.viewer.setDestinationCalendar.useMutation();
return (
<QueryCell
query={query}
customLoader={<SkeletonLoader className="mt-10" />}
success={({ data }) => {
return (
<>
{(!!data.connectedCalendars.length || !!installedCalendars.data?.items.length) && (
<>
{heading && (
<ShellSubHeading
className="mt-10 mb-0"
title={
<SubHeadingTitleWithConnections
title="Calendars"
numConnections={data.connectedCalendars.length}
/>
}
subtitle={t("configure_how_your_event_types_interact")}
actions={
<div className="flex flex-col xl:flex-row xl:space-x-5">
<div className="sm:min-w-80 block max-w-full">
<DestinationCalendarSelector
onChange={mutation.mutate}
isLoading={mutation.isLoading}
value={data.destinationCalendar?.externalId}
/>
</div>
{!!data.connectedCalendars.length && (
<div className="sm:min-w-80 inline max-w-full">
<AdditionalCalendarSelector isLoading={mutation.isLoading} />
</div>
)}
</div>
}
/>
)}
<ConnectedCalendarsList onChanged={onChanged} fromOnboarding={fromOnboarding} />
</>
)}
{fromOnboarding && (
<>
{!!query.data?.connectedCalendars.length && (
<ShellSubHeading
className="mt-4"
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
/>
)}
<CalendarList onChanged={onChanged} />
</>
)}
</>
);
}}
/>
);
}
@@ -1,57 +0,0 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { ButtonBaseProps } from "@calcom/ui/Button";
import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent";
import { Dialog } from "@calcom/ui/Dialog";
import showToast from "@calcom/ui/v2/core/notifications";
export default function DisconnectIntegration(props: {
/** Integration credential id */
id: number;
externalId?: string;
render: (renderProps: ButtonBaseProps) => JSX.Element;
onOpenChange: (isOpen: boolean) => unknown | Promise<unknown>;
}) {
const { id, externalId = "" } = props;
const { t } = useLocale();
const [modalOpen, setModalOpen] = useState(false);
const mutation = trpc.viewer.deleteCredential.useMutation({
onSettled: async () => {
await props.onOpenChange(modalOpen);
},
onSuccess: () => {
showToast("Integration deleted successfully", "success");
setModalOpen(false);
},
onError: () => {
throw new Error("Something went wrong");
},
});
return (
<>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<ConfirmationDialogContent
variety="danger"
title={t("remove_app")}
confirmBtnText={t("yes_remove_app")}
cancelBtnText="Cancel"
onConfirm={() => {
mutation.mutate({ id, externalId });
}}>
{t("are_you_sure_you_want_to_remove_this_app")}
</ConfirmationDialogContent>
</Dialog>
{props.render({
onClick() {
setModalOpen(true);
},
disabled: modalOpen,
loading: mutation.isLoading,
})}
</>
);
}
@@ -1,69 +0,0 @@
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { ButtonBaseProps } from "@calcom/ui/Button";
import { Dialog } from "@calcom/ui/Dialog";
import showToast from "@calcom/ui/v2/core/notifications";
import DeleteStripeDialogContent from "@components/dialog/DeleteStripeDialogContent";
export default function DisconnectStripeIntegration(props: {
/** Integration credential id */
id: number;
render: (renderProps: ButtonBaseProps) => JSX.Element;
onOpenChange: (isOpen: boolean) => unknown | Promise<unknown>;
}) {
const [modalOpen, setModalOpen] = useState(false);
const mutation = useMutation(
async (action: string) => {
const res = await fetch("/api/integrations", {
method: "DELETE",
body: JSON.stringify({ id: props.id, action }),
headers: {
"Content-Type": "application/json",
},
});
if (!res.ok) {
throw new Error("Something went wrong");
}
return res.json();
},
{
async onSettled() {
await props.onOpenChange(modalOpen);
},
onSuccess(data) {
showToast(data.message, "success");
setModalOpen(false);
},
}
);
return (
<>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DeleteStripeDialogContent
variety="warning"
title="Disconnect Stripe Integration"
cancelAllBookingsBtnText="Cancel all bookings"
removeBtnText="Remove payment"
cancelBtnText="Cancel"
onConfirm={() => {
mutation.mutate("cancel");
}}
onRemove={() => {
mutation.mutate("remove");
}}>
If you have unpaid and unconfirmed bookings, you must choose to cancel them or remove the required
payment field.
</DeleteStripeDialogContent>
</Dialog>
{props.render({
onClick() {
setModalOpen(true);
},
disabled: modalOpen,
loading: mutation.isLoading,
})}
</>
);
}
@@ -1,39 +0,0 @@
import Link from "next/link";
import { ReactNode } from "react";
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/List";
import classNames from "@lib/classNames";
function IntegrationListItem(props: {
imageSrc?: string;
slug: string;
name?: string;
title?: string;
description: string;
actions?: ReactNode;
children?: ReactNode;
logo: string;
}): JSX.Element {
const title = props.name || props.title;
return (
<ListItem expanded={!!props.children} className={classNames("flex-col")}>
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-3 rtl:space-x-reverse")}>
{
// eslint-disable-next-line @next/next/no-img-element
props.logo && <img className="h-10 w-10" src={props.logo} alt={title} />
}
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3">
<Link href={"/apps/" + props.slug}>{props.name || title}</Link>
</ListItemTitle>
<ListItemText component="p">{props.description}</ListItemText>
</div>
<div>{props.actions}</div>
</div>
{props.children && <div className="w-full border-t border-gray-200">{props.children}</div>}
</ListItem>
);
}
export default IntegrationListItem;
@@ -1,6 +1,6 @@
import { ReactNode } from "react";
import { Badge } from "@calcom/ui/components/badge";
import { Badge } from "@calcom/ui";
function pluralize(opts: { num: number; plural: string; singular: string }) {
if (opts.num === 0) {
@@ -2,7 +2,7 @@ import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
import React, { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import Button from "@calcom/ui/Button";
import { Button } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
@@ -1,9 +1,8 @@
import React, { SyntheticEvent, useState } from "react";
import { SyntheticEvent, useState } from "react";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import showToast from "@calcom/ui/v2/core/notifications";
import { Button, showToast } from "@calcom/ui";
const ChangePasswordSection = () => {
const [oldPassword, setOldPassword] = useState("");
@@ -3,10 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { PasswordField } from "@calcom/ui/components/form";
import { Form, Label } from "@calcom/ui/form/fields";
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -1,8 +1,6 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { Badge } from "@calcom/ui/components/badge";
import showToast from "@calcom/ui/v2/core/notifications";
import { Badge, Button, showToast } from "@calcom/ui";
const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonation: boolean }) => {
const utils = trpc.useContext();
@@ -3,9 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Form } from "@calcom/ui/components/form";
import { Button, Dialog, DialogContent, Form } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -1,7 +1,6 @@
import { useState } from "react";
import Button from "@calcom/ui/Button";
import { Badge } from "@calcom/ui/components/badge";
import { Badge, Button } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
@@ -1,6 +1,4 @@
import React from "react";
import { ShieldCheckIcon } from "@calcom/ui/Icon";
import { ShieldCheckIcon } from "@calcom/ui";
const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => {
return (
@@ -2,10 +2,7 @@ import { useMutation } from "@tanstack/react-query";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Badge } from "@calcom/ui/components/badge";
import Switch from "@calcom/ui/v2/core/Switch";
import showToast from "@calcom/ui/v2/core/notifications";
import { Badge, Icon, showToast, Switch } from "@calcom/ui";
export function CalendarSwitch(props: {
type: string;
@@ -3,10 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/components/button";
import { Form, Label } from "@calcom/ui/components/form";
import { PasswordField } from "@calcom/ui/components/form";
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog";
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -75,11 +72,7 @@ const DisableTwoFactorAuthModal = ({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
title={t("disable_2fa")}
description={t("disable_2fa_recommendation")}
type="creation"
useOwnActionButtons>
<DialogContent title={t("disable_2fa")} description={t("disable_2fa_recommendation")} type="creation">
<Form form={form} handleSubmit={handleDisable}>
<div className="mb-4">
<PasswordField
@@ -3,9 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/components/button";
import { Form, TextField } from "@calcom/ui/components/form";
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog";
import { Button, Dialog, DialogContent, Form, TextField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -132,11 +130,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
title={t("enable_2fa")}
description={setupDescriptions[step]}
type="creation"
useOwnActionButtons>
<DialogContent title={t("enable_2fa")} description={setupDescriptions[step]} type="creation">
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<form onSubmit={handleSetup}>
<div className="mb-4">
+1 -5
View File
@@ -4,11 +4,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { objectKeys } from "@calcom/lib/objectKeys";
import { TeamWithMembers } from "@calcom/lib/server/queries/teams";
import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import { TextField } from "@calcom/ui/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
import { Alert, Button, Icon, showToast, TextField } from "@calcom/ui";
import ImageUploader from "@components/ImageUploader";
import SettingInputContainer from "@components/ui/SettingInputContainer";
@@ -1,14 +1,10 @@
import { MembershipRole } from "@prisma/client";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import { TeamWithMembers } from "@calcom/lib/server/queries/teams";
import { trpc } from "@calcom/trpc/react";
import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent";
import { Dialog, DialogTrigger } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import showToast from "@calcom/ui/v2/core/notifications";
import { ConfirmationDialogContent, Dialog, DialogTrigger, Icon, showToast } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
+1 -1
View File
@@ -2,7 +2,7 @@ import Link from "next/link";
import { TeamPageProps } from "pages/team/[slug]";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { Avatar } from "@calcom/ui/components";
import { Avatar } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
+19 -7
View File
@@ -1,3 +1,4 @@
import Link from "next/link";
import React from "react";
import classNames from "@lib/classNames";
@@ -12,6 +13,7 @@ export type AvatarGroupProps = {
image: string;
title?: string;
alt?: string;
href?: string;
}[];
className?: string;
};
@@ -21,15 +23,25 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
<ul className={classNames(props.className)}>
{props.items.slice(0, props.truncateAfter).map((item, idx) => {
if (item.image != null) {
const avatar = (
<AvatarSSR
className={props.border}
imageSrc={item.image}
title={item.title}
alt={item.alt || ""}
size={props.size}
/>
);
return (
<li key={idx} className="-mr-2 inline-block">
<AvatarSSR
className={props.border}
imageSrc={item.image}
title={item.title}
alt={item.alt || ""}
size={props.size}
/>
{item.href ? (
<Link href={item.href}>
<a>{avatar}</a>
</Link>
) : (
avatar
)}
</li>
);
}
+2 -1
View File
@@ -15,6 +15,7 @@ export type AvatarProps = (
className?: string;
size?: number;
title?: string;
href?: string;
alt: string;
};
@@ -49,7 +50,7 @@ export function AvatarSSR(props: AvatarProps) {
const avatar = imgSrc ? <img alt={alt} className={className} src={imgSrc} /> : null;
return title ? (
<Tooltip.Tooltip delayDuration={300}>
<Tooltip.TooltipTrigger className="cursor-default">{avatar}</Tooltip.TooltipTrigger>
<Tooltip.TooltipTrigger asChild>{avatar}</Tooltip.TooltipTrigger>
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white">
<Tooltip.Arrow />
{title}
+1 -1
View File
@@ -2,7 +2,7 @@ import classNames from "classnames";
import React, { useState } from "react";
import { ControllerRenderProps } from "react-hook-form";
import { Icon } from "@calcom/ui/Icon";
import { Icon } from "@calcom/ui";
const EditableHeading = function EditableHeading({
value,
+1 -2
View File
@@ -1,5 +1,4 @@
import { Icon } from "@calcom/ui/Icon";
import { Tooltip } from "@calcom/ui/Tooltip";
import { Icon, Tooltip } from "@calcom/ui";
export default function InfoBadge({ content }: { content: string }) {
return (
+1 -1
View File
@@ -1,7 +1,7 @@
import classNames from "classnames";
import React, { PropsWithChildren } from "react";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Dialog, DialogContent } from "@calcom/ui";
export default function ModalContainer(
props: PropsWithChildren<{
@@ -1,41 +0,0 @@
import { cleanup, screen, render } from "@testing-library/react";
import { trpc } from "@calcom/trpc/react";
import NoCalendarConnectedAlert from "..";
// TODO: useQuery mock is not working
describe.skip("Testing NoCalendarConnectedAlert", () => {
describe("Render test", () => {
it("should render without crashing", () => {
// Disabled as its asking for full trpc useQuery response
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
jest.spyOn(trpc, "useQuery").mockReturnValue({
isSuccess: true,
isFetched: true,
data: { connectedCalendars: [], destinationCalendar: undefined },
});
render(<NoCalendarConnectedAlert />);
const testProp = screen.getByText("missing_connected_calendar");
expect(testProp).toBeTruthy();
});
it("should not render", () => {
// Disabled as its asking for full trpc useQuery response
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
jest.spyOn(trpc, "useQuery").mockReturnValue({
isSuccess: true,
isFetched: true,
data: { connectedCalendars: [1, 2], destinationCalendar: { a: "test" } },
});
render(<NoCalendarConnectedAlert />);
expect(screen.queryByText("missing_connected_calendar")).toBeNull();
});
afterEach(cleanup);
});
});
@@ -1,44 +0,0 @@
import { useTranslation } from "next-i18next";
import { Trans } from "next-i18next";
import { trpc } from "@calcom/trpc/react";
// @TODO: alert v2
import { Alert } from "@calcom/ui/Alert";
import { LinkText } from "../LinkText";
const NoCalendarConnectedAlert = () => {
const { t } = useTranslation();
const query = trpc.viewer.connectedCalendars.useQuery();
// We are not gonna show this alert till we fetch data from DB
let defaultCalendarConnected = true;
if (query.isSuccess && query.isFetched && query.data) {
defaultCalendarConnected =
query.data.connectedCalendars.length > 0 && query.data.destinationCalendar !== undefined;
}
return (
<>
{!defaultCalendarConnected && (
<Alert
severity="warning"
className="mb-4"
title={<>{t("missing_connected_calendar") as string}</>}
message={
<Trans i18nKey="connect_your_calendar_and_link">
You can connect your calendar from
<LinkText
data-testid="no-calendar-connected-alert"
href="/apps/categories/calendar"
classNameChildren="underline">
here
</LinkText>
.
</Trans>
}
/>
)}
</>
);
};
export default NoCalendarConnectedAlert;
+6 -5
View File
@@ -1,13 +1,14 @@
import React, { FC } from "react";
import Dropdown, {
import {
Button,
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuPortal,
} from "@calcom/ui/Dropdown";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components/button";
DropdownMenuTrigger,
Icon,
} from "@calcom/ui";
import { SVGComponent } from "@lib/types/SVGComponent";
@@ -1,7 +1,7 @@
import classNames from "classnames";
import { debounce, noop } from "lodash";
import { useRouter } from "next/router";
import { RefCallback, useMemo, useEffect, useState } from "react";
import { RefCallback, useEffect, useMemo, useState } from "react";
import { getPremiumPlanMode, getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
import { fetchUsername } from "@calcom/lib/fetchUsername";
@@ -11,10 +11,17 @@ import { User } from "@calcom/prisma/client";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Dialog, DialogClose, DialogContent, DialogHeader } from "@calcom/ui/Dialog";
import { Icon, StarIconSolid } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components/button";
import { Input, Label } from "@calcom/ui/components/form";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogHeader,
Icon,
Input,
Label,
StarIconSolid,
} from "@calcom/ui";
export enum UsernameChangeStatusEnum {
NORMAL = "NORMAL",
@@ -340,10 +347,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
{t("save")}
</Button>
)}
<DialogClose asChild>
<Button color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
{t("cancel")}
</Button>
<DialogClose color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
{t("cancel")}
</DialogClose>
</div>
</DialogContent>
@@ -7,9 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Dialog, DialogClose, DialogContent, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Button, Input, Label } from "@calcom/ui/components";
import { Button, Dialog, DialogClose, DialogContent, DialogHeader, Icon, Input, Label } from "@calcom/ui";
interface ICustomUsernameProps {
currentUsername: string | undefined;
@@ -196,10 +194,8 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
{t("save")}
</Button>
<DialogClose asChild>
<Button color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
{t("cancel")}
</Button>
<DialogClose color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
{t("cancel")}
</DialogClose>
</div>
</DialogContent>
+1 -1
View File
@@ -3,7 +3,7 @@ import { useEffect } from "react";
import { HexColorInput, HexColorPicker } from "react-colorful";
import { isValidHexCode, fallBackHex } from "@calcom/lib/CustomBranding";
import { Swatch } from "@calcom/ui/v2";
import { Swatch } from "@calcom/ui";
type Handler = (event: MouseEvent | Event) => void;
function useEventListener<
@@ -2,7 +2,7 @@ import React from "react";
import { Props } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Icon } from "@calcom/ui";
import Avatar from "@components/ui/Avatar";
import Select from "@components/ui/form/Select";
+1 -1
View File
@@ -3,7 +3,7 @@ import "react-calendar/dist/Calendar.css";
import "react-date-picker/dist/DatePicker.css";
import PrimitiveDatePicker from "react-date-picker/dist/entry.nostyle";
import { Icon } from "@calcom/ui/Icon";
import { Icon } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -1,28 +0,0 @@
// @see: https://github.com/wojtekmaj/react-daterange-picker/issues/91
import "@wojtekmaj/react-daterange-picker/dist/DateRangePicker.css";
import PrimitiveDateRangePicker from "@wojtekmaj/react-daterange-picker/dist/entry.nostyle";
import React from "react";
import "react-calendar/dist/Calendar.css";
import { Icon } from "@calcom/ui/Icon";
type Props = {
startDate: Date;
endDate: Date;
onDatesChange?: ((arg: { startDate: Date; endDate: Date }) => void) | undefined;
};
export const DateRangePicker = ({ startDate, endDate, onDatesChange }: Props) => {
return (
<PrimitiveDateRangePicker
className="rounded-sm border-gray-300 text-sm"
clearIcon={null}
calendarIcon={<Icon.FiCalendar className="h-5 w-5 text-gray-500" />}
rangeDivider={<Icon.FiArrowRight className="h-4 w-4 text-gray-400 ltr:mr-2 rtl:ml-2" />}
value={[startDate, endDate]}
onChange={([startDate, endDate]) => {
if (typeof onDatesChange === "function") onDatesChange({ startDate, endDate });
}}
/>
);
};
@@ -1,7 +1,7 @@
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import React from "react";
import { Icon } from "@calcom/ui/Icon";
import { Icon } from "@calcom/ui";
import classNames from "@lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
+1 -2
View File
@@ -12,8 +12,7 @@ import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import type { DecorateProcedure } from "@calcom/trpc/react/shared";
import type { AnyQueryProcedure, inferProcedureInput, inferProcedureOutput } from "@calcom/trpc/server";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Alert } from "@calcom/ui/Alert";
import Loader from "@calcom/ui/Loader";
import { Alert, Loader } from "@calcom/ui";
import type { UseTRPCQueryOptions } from "@trpc/react-query/shared";
+1 -1
View File
@@ -10,7 +10,7 @@ import { ComponentProps, ReactNode } from "react";
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
import { trpc } from "@calcom/trpc/react";
import { MetaProvider } from "@calcom/ui/v2/core/Meta";
import { MetaProvider } from "@calcom/ui";
import usePublicPage from "@lib/hooks/usePublicPage";
+18
View File
@@ -0,0 +1,18 @@
import { useRouter } from "next/router";
export default function useRouterQuery<T extends string>(name: T) {
const router = useRouter();
const existingQueryParams = router.asPath.split("?")[1];
const urlParams = new URLSearchParams(existingQueryParams);
const query = Object.fromEntries(urlParams);
const setQuery = (newValue: string | number | null | undefined) => {
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
router.replace({ query: { ...router.query, ...query, [name]: newValue } }, undefined, { shallow: true });
};
return { [name]: query[name], setQuery } as {
[K in T]: string | undefined;
} & { setQuery: typeof setQuery };
}
@@ -17,9 +17,9 @@ const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
let appsStatus: AppsStatus[] | undefined = undefined;
// Reversing to accumulate results for noEmail instances first, to then lastly, create the
// emailed booking taking into account accumulated results to send app status accurately
for (let key = 0; key < data.length; key++) {
for (let key = data.length - 1; key >= 0; key--) {
const booking = data[key];
if (key === data.length - 1) {
if (key === 0) {
const calcAppsStatus: { [key: string]: AppsStatus } = createdBookings
.flatMap((book) => (book.appsStatus !== undefined ? book.appsStatus : []))
.reduce((prev, curr) => {
@@ -39,6 +39,7 @@ const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
appsStatus,
allRecurringDates,
currentRecurringIndex: key,
noEmail: key !== 0,
});
createdBookings.push(response);
}
+19 -8
View File
@@ -1,7 +1,7 @@
require("dotenv").config({ path: "../../.env" });
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { withSentryConfig } = require("@sentry/nextjs");
const os = require("os");
const withTM = require("next-transpile-modules")([
"@calcom/app-store",
"@calcom/core",
@@ -80,6 +80,7 @@ plugins.push(withAxiom);
/** @type {import("next").NextConfig} */
const nextConfig = {
i18n,
productionBrowserSourceMaps: true,
/* We already do type check on GH actions */
typescript: {
ignoreBuildErrors: !!process.env.CI,
@@ -88,10 +89,8 @@ const nextConfig = {
eslint: {
ignoreDuringBuilds: !!process.env.CI,
},
experimental: {
images: {
unoptimized: true,
},
images: {
unoptimized: true,
},
webpack: (config) => {
config.plugins.push(
@@ -100,6 +99,13 @@ const nextConfig = {
{
from: "../../packages/app-store/**/static/**",
to({ context, absoluteFilename }) {
// Adds compatibility for windows path
if (os.platform() === "win32") {
const absoluteFilenameWin = absoluteFilename.replaceAll("\\", "/");
const contextWin = context.replaceAll("\\", "/");
const appName = /app-store\/(.*)\/static/.exec(absoluteFilenameWin);
return Promise.resolve(`${contextWin}/public/app-store/${appName[1]}/[name][ext]`);
}
const appName = /app-store\/(.*)\/static/.exec(absoluteFilename);
return Promise.resolve(`${context}/public/app-store/${appName[1]}/[name][ext]`);
},
@@ -230,9 +236,6 @@ const nextConfig = {
return redirects;
},
sentry: {
hideSourceMaps: true,
},
};
const sentryWebpackPluginOptions = {
@@ -241,6 +244,14 @@ const sentryWebpackPluginOptions = {
const moduleExports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
nextConfig.sentry = {
hideSourceMaps: true,
// Prevents Sentry from running on this Edge function, where Sentry doesn't work yet (build whould crash the api route).
excludeServerRoutes: [/\/api\/social\/og\/image\/?/],
};
}
// Sentry should be the last thing to export to catch everything right
module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN
? withSentryConfig(moduleExports, sentryWebpackPluginOptions)
+4 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.1.5",
"version": "2.3.3",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -19,11 +19,11 @@
"check-changed-files": "ts-node scripts/ts-check-changed-files.ts"
},
"engines": {
"node": ">=14.x <17",
"node": "16.x || 18.x",
"yarn": ">=1.19.0 < 2.0.0"
},
"dependencies": {
"@boxyhq/saml-jackson": "1.3.1",
"@boxyhq/saml-jackson": "1.3.6",
"@calcom/app-store": "*",
"@calcom/app-store-cli": "*",
"@calcom/core": "*",
@@ -56,13 +56,12 @@
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-toggle-group": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"@sentry/nextjs": "^7.17.3",
"@sentry/nextjs": "^7.20.0",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@tanstack/react-query": "^4.3.9",
"@vercel/edge-functions-ui": "^0.2.1",
"@vercel/og": "^0.0.19",
"@wojtekmaj/react-daterange-picker": "^3.3.1",
"accept-language-parser": "^1.5.0",
"async": "^3.2.4",
"bcryptjs": "^2.4.3",
@@ -73,7 +72,6 @@
"handlebars": "^4.7.7",
"ical.js": "^1.4.0",
"ics": "^2.37.0",
"jimp": "^0.16.1",
"kbar": "^0.1.0-beta.36",
"libphonenumber-js": "^1.10.12",
"lodash": "^4.17.21",
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { DEVELOPER_DOCS, DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
import { Icon } from "@calcom/ui/Icon";
import { Icon } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
+1 -1
View File
@@ -1,7 +1,7 @@
import Head from "next/head";
import { WEBSITE_URL } from "@calcom/lib/constants";
import Button from "@calcom/ui/Button";
import { Button } from "@calcom/ui";
export default function Error500() {
return (

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