Compare commits

...
173 Commits
Author SHA1 Message Date
Alex van Andel ad4d71d8c0 v3.0.10
release.yml / release (push) Canceled after 0s
2023-07-03 02:05:11 -04:00
Crowdin Bot b3226dc308 New Crowdin translations by Github Action 2023-07-03 05:44:04 +00:00
Joe Au-Yeung 9ef8903309 Transform URLs to array (#9890) 2023-07-03 01:43:23 -04:00
Crowdin Bot c46c9bfe90 New Crowdin translations by Github Action 2023-07-02 18:41:05 +00:00
583571247c feat: org invite billing (#9291)
* Initial commit

* Adding feature flag

* Desktop first banner, mobile pending

* Removing dead code and img

* AppInstallButtonBase

* WIP

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

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

* Change scopedMembers to orgMembers

* Change to orgUsers

* First step done

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

* Session logic to show org label

* Step 2 done, avatar not working

* List orgs and list teams specific if orgs exist

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

* Profile Page + update

* Org specific team creation

* appearance page

* Ensure members cant of org cant update settings in UI

* Fix update handler imports

* hide billing on sub teams

* Update profile slug page

* Letting duplicate slugs for teams to support orgs

* Add slug coliisions for org

* Covering null on unique clauses

* Covering null on unique clauses

* Extract to utils

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

* Supporting having the orgId in the session cookie

* Onboarding admins step

* Last step to create teams

* Update handler comments

* Upgrade ORG banner - disabled team banner for child teams

* Handle publishing ORGS

* Fix licenese issue

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

* Split into function calls to make this file more explisit

* Update parents stripe sub not teamID

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Create org membership also - billing portal page

* A11ly

* Hide create team if no valid permisisons

* Get Org members router

* Handle updating subscription if orgId

* Fix double upgrade banner

* Update constants

* Feedback

* Copy change

* Making an org avatar (temp)

* Add slug colission detection for user and team name

* Fix Import

* Remove update password func

* Fix module import over relative

* feat: organization event type filter (#9253)

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

* Missing changes to support orgs schema changes

* Fix import again

* Throw no team found before auth error

* Check if invited found user is already in differnt org

* Move to for of loop to throw errors in usenamelist

* Remove app install button sa its in 9337

* Remove i18n key not being used

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

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

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

* First step done

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

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Making sure we check requestedSlug now

---------

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

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

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

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

* First step done

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

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* Covering users and subteams, excluding non-org users

* Unpublished teams shows correctly

* Create subdomain in Vercel

* feedback

* Renaming Vercel env vars

* Vercel domain check before creation

* Supporting cal-staging.com

* Change to have vercel detect it

* vercel domain check data message error

* Remove check domain

* Making sure we check requestedSlug now

* Feedback and unneeded code

* Reverting unneeded changes

* Unneeded changes

---------

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

* Vercel subdomain creation in PROD only

* Fix router

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

* feat: organization settings general page

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

* feat: add members page

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

* chore: remove

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

* fix: use invalidate

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

* fix: delete mutation

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

* fix: remove organization id

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

* chore

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

* fix: use zod schema

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

---------

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

* Type fixes

* Use org Stripe product when upgrading

* Removed unused code

* Reverting changes

* Update UsernameTextfield.tsx

* More reverts

* Update next-auth-options.ts

* Update common.json

* Type fixes

* Include invite token for orgs

* Update org schema

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

* Reverts

* remove yarn.lock from commit

* Fix types

* feat: orgs unverified (#9415)

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

* Fix wrong banner being displayed

* Type fix

* Fix type issues

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

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

* fix missing input on trpc query

* Fix for parentId value for createProvisionalMembership

---------

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

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

---------

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

* Add Stripe payment feature

* Update Stripe payment form with valid card info

* Fill postal code if visible in Stripe test

* Implementing new e2e test

* lint fix

---------

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

* fixes on data returned

* mobile style fixes

* Update on styles and dates not being shown on graphics

* feat: update insights filters UI (#9756)

* feat: update insights filters UI

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

* Fixes for filters with new UI

* fix initialConfig lost while clearing filters

* Add single user conditional for first render

---------

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

---------

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

* fix: api key , date picker to drop down

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

---------

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Sort getAppCategories entries alphabetically

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

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

* Recategorise apps

* Sync seed-app-store categories with config files

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

* Copy video.svg to conferencing.svg

* Add messaging.svg

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

* Fix app-store-cli categories

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

* Remove outdated web3 comment in seed-app-store

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

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

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

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

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

* Restore "installed_app" string for conferencing apps

* Make linter happier

* Remove my "installed_app_conferencing_description" as this was fixed upstream

* Quick tidy up

* Add dry-run to syncAppMeta via  CRON_ENABLE_APP_SYNC env

* Replace console.log with logger in syncAppMeta

---------

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

* Apply suggestions from code review

* Update showToast.tsx

---------

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

* Add a month_view scenario

* Fix false alarm in logs when namespace is empty string

* In mobile view, we only support mobile layout

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

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

Bring back the slot reservation sistem to the new booker

## Type of change

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

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

## How should this be tested?

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

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

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


## Mandatory Tasks

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

* Used ORGANIZATIONS_ENABLED to opt-out of orgs behaviour

* Add comments

* Add a new testcase

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

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

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

---------

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

* Update apps/api/README.md

* Update apps/api/README.md

---------

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

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

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

* chore

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

---------

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

* Testing to see if extra toString() removes unicode

* Replacing unicode space

* Updated to regex

* Reverted files that were not supposed to change

* Reverted schema changes

* Added comment

* Type check

* Change function call structure

---------

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

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

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

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

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

* Dont avoid rewrite for /booked event slug

* Add /embed/embed.js test

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

* Fix overlay

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

* Derive group usernames correctly

* Revert "Debuglogs"

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

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

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

* fix: type error

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

* fix: remove filter from mobile

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

* fix: show heading when one item selected

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

---------

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

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

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

* Use DELETE with query params in Calendar Switch component

---------

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

* chore: format file

* chore: re-add comments

VSCode removed them for unknown reasons.

---------

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

* feat: welcome new contributors

* feat: add triggers welcome message

* chore: fix lint issue

* chore: typos

* Fixed spacing

* Temporarily making this runnable via Actions page

* Removed workflow_call

---------

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

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

* Fixed spacing

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

* Update swagger doc for _patch

* Resolves two NITs

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

* update 404 page to include invalid token error

* fix: use i18n

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-19 13:24:10 +01:00
Carina WollendorferandCarinaWolli 212f7a374f fix name dropdown (#9621)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-19 11:44:47 +00:00
473 changed files with 8472 additions and 4850 deletions
+11 -2
View File
@@ -59,6 +59,11 @@ SAML_CLIENT_SECRET_VERIFIER=
# PGSSLMODE='no-verify'
PGSSLMODE=
# Define which hostnames are expected for the app to work on
ALLOWED_HOSTNAMES='"cal.com","cal.dev","cal-staging.com","cal.community","cal.local:3000","localhost:3000"'
# Reserved orgs subdomains for our own usage
RESERVED_SUBDOMAINS='"app","auth","docs","design","console","go","status","api","saml","www","matrix","developer","cal","my","team","support","security","blog","learn","admin"'
# - NEXTAUTH
# @see: https://github.com/calendso/calendso/issues/263
# @see: https://next-auth.js.org/configuration/options#nextauth_url
@@ -76,6 +81,10 @@ CALCOM_TELEMETRY_DISABLED=
# ApiKey for cronjobs
CRON_API_KEY='0cc0e6c35519bba620c9360cfe3e68d0'
# Whether to automatically keep app metadata in the database in sync with the metadata/config files. When disabled, the
# sync runs in a reporting-only dry-run mode.
CRON_ENABLE_APP_SYNC=false
# Application Key for symmetric encryption and decryption
# must be 32 bytes for AES256 encryption algorithm
# You can use: `openssl rand -base64 24` to generate one
@@ -126,6 +135,7 @@ NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE=
NEXT_PUBLIC_IS_PREMIUM_NEW_PLAN=0
NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_ORG_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
@@ -172,7 +182,6 @@ CLOSECOM_API_KEY=
# Sendgrid internal sync service
SENDGRID_SYNC_API_KEY=
# Change your Brand
NEXT_PUBLIC_APP_NAME="Cal.com"
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
@@ -210,4 +219,4 @@ AUTH_BEARER_TOKEN_VERCEL=
NEW_BOOKER_ENABLED_FOR_EMBED=0
#Enables New booker for All but Embed requests
NEW_BOOKER_ENABLED_FOR_NON_EMBED=0
NEW_BOOKER_ENABLED_FOR_NON_EMBED=0
+2
View File
@@ -26,11 +26,13 @@ Fixes # (issue)
- [ ] Test B
## Mandatory Tasks
- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
## Checklist
<!-- Please remove all the irrelevant bullets to your PR -->
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
- I haven't commented my code, particularly in hard-to-understand areas
-1
View File
@@ -9,7 +9,6 @@ runs:
with:
path: gh.env
key: env-cache-${{ hashFiles('gh.env') }}
restore-keys: env-cache-
- name: Set Environment Variables
uses: tw3lveparsecs/github-actions-setvars@latest
with:
+2
View File
@@ -10,6 +10,7 @@ on:
schedule:
# Runs every day (see https://crontab.guru)
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
@@ -22,3 +23,4 @@ jobs:
days-before-pr-close: 7
stale-pr-message: "This PR is being marked as stale due to inactivity."
close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued."
operations-per-run: 100
+24
View File
@@ -0,0 +1,24 @@
name: Cron - syncAppMeta
on:
workflow_dispatch:
# "Scheduled workflows run on the latest commit on the default or base branch."
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
schedule:
# Runs “Every month at 1st (see https://crontab.guru)
- cron: "0 0 1 * *"
jobs:
cron-syncAppMeta:
env:
APP_URL: ${{ secrets.APP_URL }}
CRON_API_KEY: ${{ secrets.CRON_API_KEY }}
runs-on: ubuntu-latest
steps:
- name: cURL request
if: ${{ env.APP_URL && env.CRON_API_KEY }}
run: |
curl ${{ secrets.APP_URL }}/api/cron/syncAppMeta \
-X POST \
-H 'content-type: application/json' \
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
--fail
+3
View File
@@ -11,6 +11,7 @@ env:
INPUT_ENV_GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
INPUT_ENV_GOOGLE_LOGIN_ENABLED: true
# INPUT_ENV_CRON_API_KEY: xxx
# INPUT_ENV_CRON_ENABLE_APP_SYNC: true|false
INPUT_ENV_CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
INPUT_ENV_NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
INPUT_ENV_STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
@@ -31,6 +32,8 @@ env:
# INPUT_ENV_MS_GRAPH_CLIENT_SECRET: xxx
# INPUT_ENV_ZOOM_CLIENT_ID: xxx
# INPUT_ENV_ZOOM_CLIENT_SECRET: xxx
INPUT_ENV_SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
INPUT_ENV_SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
INPUT_ENV_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
INPUT_ENV_TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
+3
View File
@@ -34,12 +34,15 @@ jobs:
app-store:
- 'apps/web/**'
- 'packages/app-store/**'
- 'playwright.config.ts'
embed:
- 'apps/web/**'
- 'packages/embeds/**'
- 'playwright.config.ts'
embed-react:
- 'apps/web/**'
- 'packages/embeds/**'
- 'playwright.config.ts'
env:
name: Create env file
+28 -2
View File
@@ -9,7 +9,7 @@ on:
- synchronize
permissions:
pull-requests: read
pull-requests: write
jobs:
validate-pr:
@@ -17,5 +17,31 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: marocchino/sticky-pull-request-comment@v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
if: always() && (steps.lint_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
Details:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
message: |
Thank you for following the naming conventions! 🙏
@@ -0,0 +1,28 @@
name: "Welcome new contributors"
on:
issues:
types: opened
pull_request:
types: opened
permissions:
pull-requests: write
issues: write
jobs:
welcome-message:
name: Welcoming New Users
runs-on: ubuntu-latest
timeout-minutes: 10
needs: semantics
if: github.event.action == 'opened'
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: |-
Thank you for making your first Pull Request and taking the time to improve Cal.com ! ❤️🎉
Feel free to join the conversation at [slack](https://calendso.slack.com/)
issue-message: |
Thank you for opening your first issue, one of our team members will review it as soon as it possible. ❤️🎉
+2 -1
View File
@@ -8,6 +8,7 @@
"ban.spellright", // Spell check for docs
"stripe.vscode-stripe", // stripe VSCode extension
"Prisma.prisma", // syntax|format|completion for prisma
"rebornix.project-snippets" // Share useful snippets between collaborators
"rebornix.project-snippets", // Share useful snippets between collaborators
"inlang.vs-code-extension" // improved i18n DX
]
}
+1
View File
@@ -88,6 +88,7 @@ To develop locally:
5. Set up your `.env` file:
- Duplicate `.env.example` to `.env`.
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
+1 -1
View File
@@ -2,7 +2,7 @@ Copyright (c) 2020-present Cal.com, Inc.
Portions of this software are licensed as follows:
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee and https://github.com/calcom/cal.com/tree/main/apps/api/ directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
+22 -11
View File
@@ -90,6 +90,13 @@ That's where Cal.com comes in. Self-hosted or hosted by us. White-label by desig
- [Prisma.io](https://prisma.io/?ref=cal.com)
- [Daily.co](https://go.cal.com/daily)
## Contact us
Meet our sales team for any commercial inquiries.
<a href="https://cal.com/sales"><img src="https://cal.com/book-with-cal-dark.svg" alt="Book us with Cal.com"></a>
## Stay Up-to-Date
Cal.com officially launched as v.1.0 on 15th of September, however a lot of new features are coming. Watch **releases** of this repository to be notified for future updates:
@@ -121,9 +128,9 @@ Here is what you need to be able to run Cal.com.
```sh
git clone https://github.com/calcom/cal.com.git
```
> If you are on windows, run the following command on `gitbash` with admin privileges: <br>
```git clone -c core.symlinks=true https://github.com/calcom/cal.com.git``` <br>
See [docs](https://cal.com/docs/how-to-guides/how-to-troubleshoot-symbolic-link-issues-on-windows#enable-symbolic-links) for more details.
> If you are on windows, run the following command on `gitbash` with admin privileges: <br> > `git clone -c core.symlinks=true https://github.com/calcom/cal.com.git` <br>
> See [docs](https://cal.com/docs/how-to-guides/how-to-troubleshoot-symbolic-link-issues-on-windows#enable-symbolic-links) for more details.
1. Go to the project folder
@@ -158,6 +165,7 @@ yarn dx
```sh
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
```
#### Gitpod Setup
1. Click the button below to open this project in Gitpod.
@@ -166,8 +174,6 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/calcom/cal.com)
#### Manual setup
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, and `<db-port>` with their applicable values
@@ -175,24 +181,26 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
```
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
```
<details>
<summary>If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick local DB</summary>
1. [Download](https://www.postgresql.org/download/) and install postgres in your local (if you don't have it already).
2. Create your own local db by executing `createDB <DB name>`
2. Create your own local db by executing `createDB <DB name>`
3. Now open your psql shell with the DB you created: `psql -h localhost -U postgres -d <DB name>`
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
![image](https://user-images.githubusercontent.com/39329182/236612291-51d87f69-6dc1-4a23-bf4d-1ca1754e0a35.png)
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
</details>
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
- [Setup postgres DB with railway.app](https://arctype.com/postgres/setup/railway-postgres)
- [Setup postgres DB with render](https://render.com/docs/databases)
@@ -208,10 +216,11 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
```
In a production environment, run:
```sh
yarn workspace @calcom/prisma db-deploy
```
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
```sh
@@ -319,7 +328,7 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
### Heroku
<a href="https://heroku.com/deploy?template=https://github.com/calcom/cal.com">
<img width="185px" height="auto" src="https://herokucdn.com/deploy/button.svg" alt="Deploy">
<img width="185px" height="auto" src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
</a>
### Railway
@@ -490,7 +499,9 @@ following
10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings.
### Obtaining Zoho Bigin Client ID and Secret
[Follow these steps](./packages/app-store/zoho-bigin/)
## Workflows
### Setting up SendGrid for Email reminders
+34 -16
View File
@@ -1,39 +1,57 @@
# Security
Contact: security@cal.com
Contact: [security@cal.com](mailto:security@cal.com)
Based on [https://supabase.com/.well-known/security.txt](https://supabase.com/.well-known/security.txt)
At Cal.com, we consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present.
At Cal.com, we consider the security of our systems a top priority. But no
matter how much effort we put into system security, there can still be
vulnerabilities present.
If you discover a vulnerability, we would like to know about it so we can take steps to address it as quickly as possible. We would like to ask you to help us better protect our clients and our systems.
If you discover a vulnerability, we would like to know about it so we can take
steps to address it as quickly as possible. We would like to ask you to help us
better protect our clients and our systems.
## Out of scope vulnerabilities:
## Out of scope vulnerabilities
- Clickjacking on pages with no sensitive actions.
- Unauthenticated/logout/login CSRF.
- Attacks requiring MITM or physical access to a user's device.
- Any activity that could lead to the disruption of our service (DoS).
- Content spoofing and text injection issues without showing an attack vector/without being able to modify HTML/CSS.
- Content spoofing and text injection issues without showing an attack
vector/without being able to modify HTML/CSS.
- Email spoofing
- Missing DNSSEC, CAA, CSP headers
- Lack of Secure or HTTP only flag on non-sensitive cookies
- Deadlinks
## Please do the following:
## Please do the following
- E-mail your findings to [security@cal.com](mailto:security@cal.com).
- Do not run automated scanners on our infrastructure or dashboard. If you wish to do this, contact us and we will set up a sandbox for you.
- Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data,
- Do not run automated scanners on our infrastructure or dashboard. If you wish
to do this, contact us and we will set up a sandbox for you.
- Do not take advantage of the vulnerability or problem you have discovered,
for example by downloading more data than necessary to demonstrate the
vulnerability or deleting or modifying other people's data,
- Do not reveal the problem to others until it has been resolved,
- Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties,
- Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, the IP address or the URL of the affected system and a description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation.
- Do not use attacks on physical security, social engineering, distributed
denial of service, spam or applications of third parties,
- Do provide sufficient information to reproduce the problem, so we will be
able to resolve it as quickly as possible. Usually, the IP address or the URL
of the affected system and a description of the vulnerability will be
sufficient, but complex vulnerabilities may require further explanation.
## What we promise:
## What we promise
- We will respond to your report within 3 business days with our evaluation of the report and an expected resolution date,
- If you have followed the instructions above, we will not take any legal action against you in regard to the report,
- We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission,
- We will respond to your report within 3 business days with our evaluation of
the report and an expected resolution date,
- If you have followed the instructions above, we will not take any legal
action against you in regard to the report,
- We will handle your report with strict confidentiality, and not pass on your
personal details to third parties without your permission,
- We will keep you informed of the progress towards resolving the problem,
- In the public information concerning the problem reported, we will give your name as the discoverer of the problem (unless you desire otherwise), and
- We strive to resolve all problems as quickly as possible, and we would like to play an active role in the ultimate publication on the problem after it is resolved.
- In the public information concerning the problem reported, we will give your
name as the discoverer of the problem (unless you desire otherwise), and
- We strive to resolve all problems as quickly as possible, and we would like
to play an active role in the ultimate publication on the problem after it
is resolved.
+4
View File
@@ -37,6 +37,10 @@
"description": "ApiKey for cronjobs",
"value": ""
},
"CRON_ENABLE_APP_SYNC": {
"description": "Whether to automatically keep app metadata in the database in sync with the metadata/config files. When disabled, the sync runs in a reporting-only dry-run mode.",
"value": "false"
},
"SEND_FEEDBACK_EMAIL": {
"description": "Send feedback email",
"value": ""
+42
View File
@@ -0,0 +1,42 @@
The Cal.com Commercial License (the “Commercial License”)
Copyright (c) 2020-present Cal.com, Inc
With regard to the Cal.com Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Cal.com Subscription Terms available
at https://cal.com/terms, or other agreements governing
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
and otherwise have a valid Cal.com Enterprise Edition subscription ("Commercial Subscription")
for the correct number of hosts as defined in the "Commercial Terms ("Hosts"). Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Commercial Subscription for the correct number of hosts.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
This Commercial License applies only to the part of this Software that is not distributed under
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
in part, is copyrighted under the AGPLv3 license. The full text of this Commercial License shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Cal.com Software, those
components are licensed under the original license provided by the owner of the
applicable component.
+1 -1
View File
@@ -7,7 +7,7 @@
<a href="https://cal.com/docs/enterprise-features/api#api-server-specifications">Read the API docs</a>
</div>
# Cal.com Public API
# Commercial Cal.com Public API
Welcome to the Public API ("/apps/api") of the Cal.com.
+2 -2
View File
@@ -32,9 +32,9 @@
"@sentry/nextjs": "^7.20.0",
"bcryptjs": "^2.4.3",
"memory-cache": "^0.2.0",
"next": "~13.2.1",
"next": "^13.4.6",
"next-api-middleware": "^1.0.1",
"next-axiom": "^0.16.0",
"next-axiom": "^0.17.0",
"next-swagger-doc": "^0.3.6",
"next-validations": "^0.2.0",
"typescript": "^4.9.4",
+12 -1
View File
@@ -35,6 +35,9 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
* status:
* type: string
* description: 'Acceptable values one of ["ACCEPTED", "PENDING", "CANCELLED", "REJECTED"]'
* description:
* type: string
* description: 'Description of the meeting'
* examples:
* editBooking:
* value:
@@ -79,7 +82,15 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
* "startTime": "2023-05-24T13:00:00.000Z",
* "endTime": "2023-05-24T13:30:00.000Z",
* "metadata": {},
* "status": "CANCELLED"
* "status": "CANCELLED",
* "responses": {
* "email": "john.doe@example.com",
* "name": "John Doe",
* "location": {
* "optionValue": "",
* "value": "inPerson"
* }
* }
* }
* }
* 400:
+69 -70
View File
@@ -26,13 +26,10 @@ import { defaultResponder } from "@calcom/lib/server";
* required:
* - eventTypeId
* - start
* - name
* - email
* - responses
* - timeZone
* - language
* - metadata
* - customInputs
* - location
* properties:
* eventTypeId:
* type: integer
@@ -45,29 +42,33 @@ import { defaultResponder } from "@calcom/lib/server";
* type: string
* format: date-time
* description: 'End time of the Event'
* name:
* type: string
* description: 'Name of the Attendee'
* email:
* type: string
* format: email
* description: 'Email ID of the Attendee'
* responses:
* type: object
* required:
* - name
* - email
* - location
* properties:
* name:
* type: string
* description: 'Attendee full name'
* email:
* type: string
* format: email
* description: 'Attendee email address'
* location:
* type: string
* description: 'Meeting location'
* metadata:
* type: object
* properties: {}
* description: 'Any metadata associated with the booking'
* timeZone:
* type: string
* description: 'TimeZone of the Attendee'
* language:
* type: string
* description: 'Language of the Attendee'
* metadata:
* type: object
* properties: {}
* description: 'Any metadata associated with the booking'
* customInputs:
* type: array
* items: {}
* location:
* type: string
* description: 'Meeting location'
* title:
* type: string
* description: 'Booking event title'
@@ -96,13 +97,14 @@ import { defaultResponder } from "@calcom/lib/server";
* "eventTypeId": 2323232,
* "start": "2023-05-24T13:00:00.000Z",
* "end": "2023-05-24T13:30:00.000Z",
* "name": "Hello Hello",
* "email": "hello@gmail.com",
* "responses":{
* "name": "Hello Hello",
* "email": "hello@gmail.com",
* "metadata": {},
* "location": "Calcom HQ",
* },
* "timeZone": "Europe/London",
* "language": "en",
* "metadata": {},
* "customInputs": [],
* "location": "Calcom HQ",
* "title": "Debugging between Syed Ali Shahbaz and Hello Hello",
* "description": null,
* "status": "PENDING",
@@ -117,53 +119,50 @@ import { defaultResponder } from "@calcom/lib/server";
* content:
* application/json:
* examples:
* bookings:
* booking created successfully example:
* value:
* {
* "id": 11223344,
* "uid": "5yUjmAYTDF6MXo98re8SkX",
* "userId": 123,
* "eventTypeId": 2323232,
* "title": "Debugging between Syed Ali Shahbaz and Hello Hello",
* "description": null,
* "customInputs": {},
* "responses": null,
* "startTime": "2023-05-24T13:00:00.000Z",
* "endTime": "2023-05-24T13:30:00.000Z",
* "location": "Calcom HQ",
* "createdAt": "2023-04-19T10:17:58.580Z",
* "updatedAt": null,
* "status": "PENDING",
* "paid": false,
* "destinationCalendarId": 2180,
* "cancellationReason": null,
* "rejectionReason": null,
* "dynamicEventSlugRef": null,
* "dynamicGroupSlugRef": null,
* "rescheduled": null,
* "fromReschedule": null,
* "recurringEventId": null,
* "smsReminderNumber": null,
* "scheduledJobs": [],
* "metadata": {},
* "isRecorded": false,
* "user": {
* "email": "test@cal.com",
* "name": "Syed Ali Shahbaz",
* "timeZone": "Asia/Calcutta"
* },
* "attendees": [
* {
* "id": 12345,
* "email": "hello@gmail.com",
* "name": "Hello Hello",
* "timeZone": "Europe/London",
* "locale": "en",
* "bookingId": 11223344
* "booking": {
* "id": 91,
* "userId": 5,
* "description": "",
* "eventTypeId": 7,
* "uid": "bFJeNb2uX8ANpT3JL5EfXw",
* "title": "60min between Pro Example and John Doe",
* "startTime": "2023-05-25T09:30:00.000Z",
* "endTime": "2023-05-25T10:30:00.000Z",
* "attendees": [
* {
* "email": "john.doe@example.com",
* "name": "John Doe",
* "timeZone": "Asia/Kolkata",
* "locale": "en"
* }
* ],
* "user": {
* "email": "pro@example.com",
* "name": "Pro Example",
* "timeZone": "Asia/Kolkata",
* "locale": "en"
* },
* "payment": [
* {
* "id": 1,
* "success": true,
* "paymentOption": "ON_BOOKING"
* }
* ],
* "metadata": {},
* "status": "ACCEPTED",
* "responses": {
* "email": "john.doe@example.com",
* "name": "John Doe",
* "location": {
* "optionValue": "",
* "value": "inPerson"
* }
* }
* ],
* "payment": [],
* "references": []
* }
* }
* 400:
* description: |
@@ -2,7 +2,7 @@ import { CAL_URL } from "@calcom/lib/constants";
export default function getCalLink(eventType: {
team?: { slug: string | null } | null;
owner: { username: string | null } | null;
owner?: { username: string | null } | null;
users?: { username: string | null }[];
slug: string;
}) {
+5 -5
View File
@@ -26,15 +26,15 @@ export function VariantsTable({
<table>
<RowTitles titles={titles} />
{columns.map((column) => (
<tr className="p-2 pr-6 pb-6" key={column.props.variant}>
<tr className="p-2 pb-6 pr-6" key={column.props.variant}>
<th
className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]"
className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]"
key={column.props.variant}>
{column.props.variant}
</th>
{React.Children.count(column.props.children) &&
React.Children.map(column.props.children, (cell) => (
<td className="p-2 pr-6 pb-6" style={{ minWidth: `${columnMinWidth}px` }}>
<td className="p-2 pb-6 pr-6" style={{ minWidth: `${columnMinWidth}px` }}>
{cell}
</td>
))}
@@ -70,9 +70,9 @@ export function VariantRow({ children }: RowProps) {
export function RowTitles({ titles }: { titles: string[] }) {
return (
<tr>
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" />
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" />
{titles.map((title) => (
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
{title}
</th>
))}
+5 -5
View File
@@ -13,14 +13,14 @@
"@radix-ui/react-avatar": "^1.0.0",
"@radix-ui/react-collapsible": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-id": "^1.0.0",
"@radix-ui/react-popover": "^1.0.2",
"@radix-ui/react-radio-group": "^1.0.0",
"@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"next": "^13.2.1",
"next": "^13.4.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook-addon-rtl-direction": "^0.0.19"
@@ -37,8 +37,8 @@
"@storybook/react": "^6.5.13",
"@storybook/testing-library": "^0.0.13",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"@vitejs/plugin-react": "^2.1.0",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^2.2.0",
"autoprefixer": "^10.4.12",
"babel-loader": "^8.2.5",
"fs": "^0.0.1-security",
@@ -48,7 +48,7 @@
"storybook-addon-designs": "^6.3.1",
"storybook-addon-next": "^1.6.9",
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.2.1",
"tailwindcss": "^3.3.1",
"typescript": "^4.9.4",
"vite": "^4.1.2"
}
@@ -209,6 +209,7 @@
--cal-bg-success: #E2FBE8;
--cal-bg-attention: #FCEED8;
--cal-bg-error: #F9E3E2;
--cal-bg-dark-error: #752522;
/* Borders */
@@ -216,6 +217,7 @@
--cal-border: #D1D5DB;
--cal-border-subtle:#E5E7EB;
--cal-border-muted:#F3F4F6;
--cal-border-error: #aa2e26;
/* Content/Text */
--cal-text-emphasis: #111827;
@@ -251,6 +253,7 @@
--cal-bg-success: #E2FBE8;
--cal-bg-attention: #FCEED8;
--cal-bg-error: #F9E3E2;
--cal-bg-dark-error: #752522;
/* Borders */
@@ -258,6 +261,7 @@
--cal-border: #444444;
--cal-border-subtle: #2B2B2B;
--cal-border-muted: #1C1C1C;
--cal-border-error: #aa2e26;
/* Content/Text */
--cal-text-emphasis: #F3F4F6;
+2 -2
View File
@@ -14,7 +14,7 @@
"dependencies": {
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "^13.2.1",
"next": "^13.4.6",
"openapi-snippet": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -23,7 +23,7 @@
"devDependencies": {
"@types/node": "16.9.1",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"@types/react-dom": "^18.0.9",
"typescript": "^4.9.4"
}
}
+89 -23
View File
@@ -10,13 +10,14 @@ import { components } from "react-select";
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
import { useFlagMap } from "@calcom/features/flags/context/provider";
import { APP_NAME, EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { APP_NAME, EMBED_LIB_URL, IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import {
Button,
Dialog,
DialogClose,
DialogFooter,
DialogContent,
HorizontalTabs,
Label,
@@ -38,13 +39,21 @@ const enum Theme {
dark = "dark",
}
const EMBED_CAL_ORIGIN = WEBAPP_URL;
const EMBED_CAL_JS_URL = `${WEBAPP_URL}/embed/embed.js`;
type PreviewState = {
inline: {
width: string;
height: string;
};
theme: Theme;
floatingPopup: Record<string, string | boolean | undefined>;
floatingPopup: {
config?: {
layout: BookerLayouts;
};
[key: string]: string | boolean | undefined | Record<string, string>;
};
elementClick: Record<string, string>;
palette: {
brandColor: string;
@@ -175,14 +184,24 @@ const Codes: Record<string, Record<string, (...args: any[]) => string>> = {
return code`
import Cal, { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";
function MyComponent() {
export default function MyApp() {
useEffect(()=>{
(async function () {
const cal = await getCalApi();
${uiInstructionCode}
})();
}, [])
return <Cal calLink="${calLink}" style={{width:"${width}",height:"${height}",overflow:"scroll"}} />;
return <Cal
calLink="${calLink}"
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
${previewState.layout ? "config={{layout: '" + previewState.layout + "'}}" : ""}${
IS_SELF_HOSTED
? `
calOrigin="${EMBED_CAL_ORIGIN}"
calJsUrl="${EMBED_CAL_JS_URL}"`
: ""
}
/>;
};`;
},
"floating-popup": ({
@@ -193,38 +212,60 @@ function MyComponent() {
uiInstructionCode: string;
}) => {
return code`
import Cal, { getCalApi } from "@calcom/embed-react";
import { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";
function MyComponent() {
export default function App() {
useEffect(()=>{
(async function () {
const cal = await getCalApi();
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
cal("floatingButton", ${floatingButtonArg});
${uiInstructionCode}
})();
}, [])
};`;
},
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
"element-click": ({
calLink,
uiInstructionCode,
previewState,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState;
}) => {
return code`
import Cal, { getCalApi } from "@calcom/embed-react";
import { getCalApi } from "@calcom/embed-react";
import { useEffect } from "react";
function MyComponent() {
export default function App() {
useEffect(()=>{
(async function () {
const cal = await getCalApi();
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
${uiInstructionCode}
})();
}, [])
return <button data-cal-link="${calLink}" />;
return <button
data-cal-link="${calLink}"${IS_SELF_HOSTED ? `\ndata-cal-origin="${EMBED_CAL_ORIGIN}"` : ""}
${`data-cal-config='${JSON.stringify({
layout: previewState.layout,
})}'`}
>Click me</button>;
};`;
},
},
HTML: {
inline: ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
inline: ({
calLink,
uiInstructionCode,
previewState,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState;
}) => {
return code`Cal("inline", {
elementOrSelector:"#my-cal-inline",
calLink: "${calLink}"
calLink: "${calLink}",
layout: "${previewState.layout}"
});
${uiInstructionCode}`;
@@ -240,8 +281,22 @@ ${uiInstructionCode}`;
return code`Cal("floatingButton", ${floatingButtonArg});
${uiInstructionCode}`;
},
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
return code`// Important: Make sure to add \`data-cal-link="${calLink}"\` attribute to the element you want to open Cal on click
"element-click": ({
calLink,
uiInstructionCode,
previewState,
}: {
calLink: string;
uiInstructionCode: string;
previewState: PreviewState;
}) => {
return code`
// Important: Please add following attributes to the element you want to open Cal on click
// \`data-cal-link="${calLink}"\`
// \`data-cal-config='${JSON.stringify({
layout: previewState.layout,
})}'\`
${uiInstructionCode}`;
},
},
@@ -298,6 +353,7 @@ const getEmbedTypeSpecificString = ({
} else if (embedType === "floating-popup") {
const floatingButtonArg = {
calLink,
...(IS_SELF_HOSTED ? { calOrigin: EMBED_CAL_ORIGIN } : null),
...previewState.floatingPopup,
};
return frameworkCodes[embedType]({
@@ -710,13 +766,13 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
height: "100%",
},
theme: Theme.auto,
layout: BookerLayouts.MONTH_VIEW,
floatingPopup: {},
elementClick: {},
hideEventTypeDetails: false,
palette: {
brandColor: "#000000",
},
layout: BookerLayouts.MONTH_VIEW,
});
const close = () => {
@@ -778,8 +834,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
name: "ui",
arg: {
theme: previewState.theme,
hideEventTypeDetails: previewState.hideEventTypeDetails,
layout: previewState.layout,
hideEventTypeDetails: previewState.hideEventTypeDetails,
styles: {
branding: {
...previewState.palette,
@@ -1000,7 +1056,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div>
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div className="whitespace-nowrap">Text color</div>
<div className="mt-2 mb-6 w-40 xl:mt-0 xl:w-full">
<div className="mb-6 mt-2 w-40 xl:mt-0 xl:w-full">
<ColorPicker
className="w-[130px]"
popoverAlign="start"
@@ -1102,8 +1158,15 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
return;
}
setPreviewState((previewState) => {
const config = {
...(previewState.floatingPopup.config ?? {}),
layout: option.value,
};
return {
...previewState,
floatingPopup: {
config,
},
layout: option.value,
};
});
@@ -1144,8 +1207,12 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
/>
)}
</div>
<div className={router.query.embedTabName == "embed-preview" ? "block" : "hidden"} />
<div className="mt-8 flex flex-row-reverse gap-x-2">
<div className={router.query.embedTabName == "embed-preview" ? "mt-2 block" : "hidden"} />
<DialogFooter
className="mt-10 flex flex-row-reverse gap-x-2"
showDivider
customDividerClassNames="w-2/3">
<DialogClose />
{tab.type === "code" ? (
<Button
type="submit"
@@ -1160,8 +1227,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
{t("copy_code")}
</Button>
) : null}
<DialogClose />
</div>
</DialogFooter>
</div>
);
})}
+1 -1
View File
@@ -71,7 +71,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
isCurrent
? "text-emphasis border-gray-900"
: "hover:border-default hover:text-default text-subtle border-transparent",
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
"group inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
className
)}
aria-current={isCurrent ? "page" : undefined}>
+7 -7
View File
@@ -85,7 +85,7 @@ const Component = ({
return (
<div className="relative flex-1 flex-col items-start justify-start px-4 md:flex md:px-8 lg:flex-row lg:px-0">
{hasDescriptionItems && (
<div className="align-center bg-subtle mb-4 -ml-4 -mr-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:mb-8 md:-ml-8 md:-mr-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
<div className="align-center bg-subtle -ml-4 -mr-4 mb-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:-ml-8 md:-mr-8 md:mb-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
{descriptionItems ? (
descriptionItems.map((descriptionItem, index) =>
typeof descriptionItem === "object" ? (
@@ -260,7 +260,7 @@ const Component = ({
)}
</span>
<h4 className="text-emphasis mt-8 mb-2 font-semibold ">{t("contact")}</h4>
<h4 className="text-emphasis mb-2 mt-8 font-semibold ">{t("contact")}</h4>
<ul className="prose-sm -ml-1 -mr-1 leading-5">
{docs && (
<li>
@@ -269,7 +269,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis text-sm font-normal no-underline hover:underline"
href={docs}>
<BookOpen className="text-subtle mr-1 -mt-1 inline h-4 w-4" />
<BookOpen className="text-subtle -mt-1 mr-1 inline h-4 w-4" />
{t("documentation")}
</a>
</li>
@@ -281,7 +281,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={website}>
<ExternalLink className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<ExternalLink className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{website.replace("https://", "")}
</a>
</li>
@@ -293,7 +293,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={"mailto:" + email}>
<Mail className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<Mail className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{email}
</a>
@@ -306,7 +306,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={tos}>
<File className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<File className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{t("terms_of_service")}
</a>
</li>
@@ -318,7 +318,7 @@ const Component = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={privacy}>
<Shield className="text-subtle mr-1 -mt-px inline h-4 w-4" />
<Shield className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{t("privacy_policy")}
</a>
</li>
@@ -28,8 +28,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
{emptyStore ? (
<EmptyScreen
Icon={AlertCircle}
headline={t("no_apps")}
description={isAdmin ? "You can enable apps in the settings" : ""}
headline={isAdmin ? t("no_apps") : t("no_apps_configured")}
description={isAdmin ? t("enable_in_settings") : t("please_contact_admin")}
buttonText={isAdmin ? t("apps_settings") : ""}
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
/>
@@ -1,6 +1,4 @@
import { z } from "zod";
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Tooltip } from "@calcom/ui";
@@ -23,20 +21,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
return null;
}
const translateAbleKeys = [
"in_person_attendee_address",
"in_person",
"attendee_phone_number",
"link_meeting",
"organizer_phone_number",
];
const locationKey = z.string().default("").parse(locationKeyToString(location));
const translatedLocation = location.type.startsWith("integrations:")
? eventLocationType.label
: translateAbleKeys.includes(locationKey)
? t(locationKey)
: locationKey;
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
return (
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
@@ -83,7 +83,7 @@ const BookingDescription: FC<Props> = (props) => {
size="sm"
truncateAfter={3}
/>
<h2 className="text-default mt-1 mb-2 break-words text-sm font-medium ">
<h2 className="text-default mb-2 mt-1 break-words text-sm font-medium ">
{eventType.team?.parent?.name} {profile.name}
</h2>
<h1 className="font-cal text-emphasis mb-6 break-words text-2xl font-semibold leading-none">
@@ -19,7 +19,7 @@ const BookingDescriptionPayment = (props: {
return (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<CreditCard className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{paymentAppData.paymentOption === "HOLD" ? (
<>{props.t("no_show_fee_amount", params)}</>
) : (
@@ -374,7 +374,7 @@ function BookingListItem(booking: BookingItemProps) {
</td>
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClickTableData}>
{/* Time and Badges for mobile */}
<div className="w-full pt-4 pb-2 sm:hidden">
<div className="w-full pb-2 pt-4 sm:hidden">
<div className="flex w-full items-center justify-between sm:hidden">
<div className="text-emphasis text-sm leading-6">{startTime}</div>
<div className="text-subtle pr-2 text-sm">
@@ -423,7 +423,7 @@ function BookingListItem(booking: BookingItemProps) {
<span> </span>
{paymentAppData.enabled && !booking.paid && booking.payment.length && (
<Badge className="ms-2 me-2 hidden sm:inline-flex" variant="orange">
<Badge className="me-2 ms-2 hidden sm:inline-flex" variant="orange">
{t("pending_payment")}
</Badge>
)}
@@ -64,7 +64,7 @@ export default function CancelBooking(props: Props) {
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-4 w-full "
className="mb-4 mt-2 w-full "
rows={3}
/>
<div className="flex flex-col-reverse rtl:space-x-reverse ">
@@ -93,7 +93,7 @@ export default function CancelBooking(props: Props) {
headers: {
"Content-Type": "application/json",
},
method: "DELETE",
method: "POST",
});
if (res.status >= 200 && res.status < 300) {
@@ -25,7 +25,7 @@ function SkeletonItem() {
</div>
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
<div className="mt-4 hidden flex-shrink-0 sm:ml-5 sm:mt-0 lg:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<SkeletonText className="h-6 w-16" />
<SkeletonText className="h-6 w-32" />
@@ -191,9 +191,9 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
)}
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<RefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<RefreshCcw className="float-left ml-[2px] mt-[7px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
<p className="-ml-2 mb-1 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
@@ -218,7 +218,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
)}
{paymentAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<CreditCard className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{paymentAppData.paymentOption === "HOLD" ? (
<>
{t("no_show_fee_amount", {
@@ -534,7 +534,7 @@ const BookingPage = ({
)}>
<div className="sm:flex">
{showEventTypeDetails && (
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
<div className="sm:border-subtle text-default flex flex-col px-6 pb-0 pt-6 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
@@ -561,7 +561,7 @@ const BookingPage = ({
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
{!rescheduleUid && eventType?.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((timeFormatted, key) => (
@@ -577,11 +577,11 @@ const BookingPage = ({
</div>
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
<p className="mb-2 mt-8 text-sm " data-testid="former_time_p">
{t("former_time")}
</p>
<p className="line-through ">
<Calendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<Calendar className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{isClientTimezoneAvailable &&
typeof booking.startTime === "string" &&
parseDateTimeWithTimeZone(booking.startTime, i18n.language, timezone, {
@@ -259,7 +259,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
{booking && (
<>
<p className="text-emphasis mt-6 mb-2 ml-1 text-sm font-bold">{t("current_location")}:</p>
<p className="text-emphasis mb-2 ml-1 mt-6 text-sm font-bold">{t("current_location")}:</p>
<p className="text-emphasis mb-2 ml-1 text-sm">
{getHumanReadableLocationValue(booking.location, t)}
</p>
@@ -49,7 +49,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
<div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} />
<p className="text-subtle text-sm">{t("reschedule_modal_description")}</p>
<p className="text-emphasis mt-6 mb-2 text-sm font-bold">
<p className="text-emphasis mb-2 mt-6 text-sm font-bold">
{t("reason_for_reschedule_request")}
<span className="text-subtle font-normal"> (Optional)</span>
</p>
+1 -1
View File
@@ -35,7 +35,7 @@ const ErrorDebugPanel: React.FC<{ error: Props["error"]; children?: never }> = (
{debugMap.map(([key, value]) => {
if (value !== undefined) {
return (
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<dt className="text-emphasis text-sm font-bold">{key}</dt>
<dd className="text-emphasis mt-1 text-sm sm:col-span-2 sm:mt-0">{value}</dd>
</div>
@@ -84,7 +84,7 @@ const CustomEventTypeModalForm: FC<CustomEventTypeModalFormProps> = (props) => {
backgroundImage: "url(/calendar-preview.svg)",
}}>
<div className="m-auto flex items-center justify-center self-stretch">
<div className="bg-subtle mt-3 ml-11 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
<div className="bg-subtle ml-11 mt-3 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
<p className="text-emphasis overflow-hidden text-ellipsis p-1.5 font-medium">{previewText}</p>
<p className="text-default ml-1.5 text-[10px] font-normal">8 - 10 AM</p>
</div>
@@ -38,6 +38,7 @@ const generateHashedLink = (id: number) => {
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
const { data: user } = trpc.viewer.me.useQuery();
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
@@ -49,6 +50,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const bookingFields: Prisma.JsonObject = {};
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
const selectedThemeIsDark =
user?.theme === "dark" ||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
eventType.bookingFields.forEach(({ name }) => {
bookingFields[name] = name + " input";
@@ -153,7 +157,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
color="minimal"
size="sm"
aria-label="edit custom name"
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
onClick={() => setShowEventNameTip((old) => !old)}>
<Edit className="h-4 w-4" />
</Button>
@@ -162,7 +166,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</div>
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
<div>
<BookerLayoutSelector fallbackToUserSettings />
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} />
</div>
<hr className="border-subtle" />
<FormBuilder
@@ -216,7 +220,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
onChange(e ? value : "");
}}>
{/* Textfield has some margin by default we remove that so we can keep consistent alignment */}
<div className="lg:-ml-2 lg:-mb-2">
<div className="lg:-mb-2 lg:-ml-2">
<TextField
className="w-full"
label={t("redirect_success_booking")}
@@ -268,7 +272,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
color="minimal"
size="sm"
type="button"
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
aria-label="copy link"
onClick={() => {
navigator.clipboard.writeText(placeholderHashedLink);
@@ -171,7 +171,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
return (
<div className="space-y-8">
<div className="space-y-4 lg:space-y-8">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<div className="w-full">
<Label htmlFor="beforeBufferTime">
{t("before_event")}
@@ -245,7 +245,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/>
</div>
</div>
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<div className="w-full">
<Label htmlFor="minimumBookingNotice">
{t("minimum_booking_notice")}
@@ -416,7 +416,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
</div>
)}
{period.type === "RANGE" && (
<div className="ms-2 me-2 inline-flex space-x-2 rtl:space-x-reverse">
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
<Controller
name="periodDates"
control={formMethods.control}
@@ -437,7 +437,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/>
</div>
)}
{period.suffix ? <span className="ms-2 me-2">&nbsp;{period.suffix}</span> : null}
{period.suffix ? <span className="me-2 ms-2">&nbsp;{period.suffix}</span> : null}
</div>
);
})}
@@ -117,8 +117,16 @@ export const EventSetupTab = (
const [selectedLocation, setSelectedLocation] = useState<LocationOption | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata?.multipleDuration);
const locationOptions = props.locationOptions.filter((option) => {
return !team ? option.label !== "Conferencing" : true;
const locationOptions = props.locationOptions.map((locationOption) => {
const options = locationOption.options.filter((option) => {
// Skip "Organizer's Default App" for non-team members
return !team ? option.label !== t("organizer_default_conferencing_app") : true;
});
return {
...locationOption,
options,
};
});
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
@@ -284,7 +292,7 @@ export const EventSetupTab = (
return (
<li
key={`${location.type}${index}`}
className="border-default text-default mb-2 h-9 rounded-md border py-1.5 px-2 hover:cursor-pointer">
className="border-default text-default mb-2 h-9 rounded-md border px-2 py-1.5 hover:cursor-pointer">
<div className="flex items-center justify-between">
<div className="flex items-center">
<img
@@ -298,7 +306,7 @@ export const EventSetupTab = (
)}
alt={`${eventLocationType.label} logo`}
/>
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
<span className="ms-1 line-clamp-1 text-sm">{eventLabel}</span>
</div>
<div className="flex">
<button
@@ -332,7 +340,7 @@ export const EventSetupTab = (
location.type === MeetLocationType && destinationCalendar?.integration !== "google_calendar"
) && (
<div className="text-default flex text-sm">
<Check className="mt-0.5 mr-1.5 h-2 w-2.5" />
<Check className="mr-1.5 mt-0.5 h-2 w-2.5" />
<Trans i18nKey="event_type_requres_google_cal">
<p>
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
@@ -369,7 +369,7 @@ function EventTypeSingleLayout({
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuSeparator />
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between px-4 py-2">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
@@ -0,0 +1,52 @@
import { Trans } from "next-i18next";
import type { ChildrenEventType } from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { ConfirmationDialogContent, Dialog } from "@calcom/ui";
interface ManagedEventDialogProps {
slugExistsChildrenDialogOpen: ChildrenEventType[];
slug: string;
onOpenChange: () => void;
isLoading: boolean;
onConfirm: (e: { preventDefault: () => void }) => void;
}
export default function ManagedEventDialog(props: ManagedEventDialogProps) {
const { t } = useLocale();
const { slugExistsChildrenDialogOpen, slug, onOpenChange, isLoading, onConfirm } = props;
return (
<Dialog open={slugExistsChildrenDialogOpen.length > 0} onOpenChange={onOpenChange}>
<ConfirmationDialogContent
isLoading={isLoading}
variety="warning"
title={t("managed_event_dialog_title", {
slug,
count: slugExistsChildrenDialogOpen.length,
})}
confirmBtnText={t("managed_event_dialog_confirm_button", {
count: slugExistsChildrenDialogOpen.length,
})}
cancelBtnText={t("go_back")}
onConfirm={onConfirm}>
<p className="mt-5">
<Trans
i18nKey="managed_event_dialog_information"
values={{
names: `${slugExistsChildrenDialogOpen
.map((ch) => ch.owner.name)
.slice(0, -1)
.join(", ")} ${
slugExistsChildrenDialogOpen.length > 1 ? t("and") : ""
} ${slugExistsChildrenDialogOpen.map((ch) => ch.owner.name).slice(-1)}`,
slug,
}}
count={slugExistsChildrenDialogOpen.length}
/>
</p>{" "}
<p className="mt-5">{t("managed_event_dialog_clarification")}</p>
</ConfirmationDialogContent>
</Dialog>
);
}
@@ -32,11 +32,11 @@ function SkeletonItem() {
<div className="">
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
<li className="flex items-center whitespace-nowrap">
<Clock className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
<Clock className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<SkeletonText className="h-4 w-12" />
</li>
<li className="flex items-center whitespace-nowrap">
<User className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
<User className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<SkeletonText className="h-4 w-12" />
</li>
</ul>
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
import { Button, Dialog, DialogContent, Form, PasswordField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -75,14 +75,12 @@ const DisableTwoFactorAuthModal = ({ onDisable, onCancel }: DisableTwoFactorAuth
{...form.register("password")}
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
/>
<Label className="mt-4"> {t("2fa_code")}</Label>
<TwoFactor center={false} />
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit" className="ms-2 me-2" disabled={isDisabling}>
<Button type="submit" className="me-2 ms-2" disabled={isDisabling}>
{t("disable")}
</Button>
<Button color="secondary" onClick={onCancel}>
@@ -19,7 +19,7 @@ const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonati
return (
<>
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
<div>
<div className="flex flex-row items-center">
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">
@@ -178,19 +178,19 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<Button
type="submit"
className="ms-2 me-2"
className="me-2 ms-2"
onClick={handleSetup}
disabled={password.length === 0 || isSubmitting}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.DisplayQrCode} current={step}>
<Button type="submit" className="ms-2 me-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
<Button type="submit" className="me-2 ms-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<Button type="submit" className="ms-2 me-2" disabled={isSubmitting}>
<Button type="submit" className="me-2 ms-2" disabled={isSubmitting}>
{t("enable")}
</Button>
</WithStep>
@@ -14,7 +14,7 @@ const TwoFactorAuthSection = ({ twoFactorEnabled }: { twoFactorEnabled: boolean
return (
<>
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
<div>
<div className="flex flex-row items-center">
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">{t("2fa")}</h2>
@@ -6,7 +6,7 @@ const TwoFactorModalHeader = ({ title, description }: { title: string; descripti
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<Shield className="text-inverted h-6 w-6" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<h3 className="font-cal text-emphasis text-lg font-medium leading-6" id="modal-title">
{title}
</h3>
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
import { Button, Dialog, DialogContent, DialogFooter, Form, PasswordField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -74,7 +74,7 @@ const DisableTwoFactorAuthModal = ({
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent title={t("disable_2fa")} description={t("disable_2fa_recommendation")} type="creation">
<Form form={form} handleSubmit={handleDisable}>
<div className="mb-4">
<div className="mb-8">
<PasswordField
labelProps={{
className: "block text-sm font-medium text-default",
@@ -82,21 +82,19 @@ const DisableTwoFactorAuthModal = ({
{...form.register("password")}
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
/>
<Label className="mt-4"> {t("2fa_code")}</Label>
<TwoFactor center={false} />
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit" className="ms-2 me-2" data-testid="disable-2fa" disabled={isDisabling}>
{t("disable")}
</Button>
<DialogFooter showDivider className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</div>
<Button type="submit" className="me-2 ms-2" data-testid="disable-2fa" disabled={isDisabling}>
{t("disable")}
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
@@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogContent, Form, TextField } from "@calcom/ui";
import { Button, Dialog, DialogContent, DialogFooter, Form, TextField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -150,20 +150,20 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
</WithStep>
<WithStep step={SetupStep.DisplayQrCode} current={step}>
<>
<div className="flex justify-center">
<div className="-mt-3 flex justify-center">
{
// eslint-disable-next-line @next/next/no-img-element
<img src={dataUri} alt="" />
}
</div>
<p data-testid="two-factor-secret" className="text-center font-mono text-xs">
<p data-testid="two-factor-secret" className="mb-4 text-center font-mono text-xs">
{secret}
</p>
</>
</WithStep>
<Form handleSubmit={handleEnable} form={form}>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<div className="mb-4">
<div className="-mt-4 pb-2">
<TwoFactor center />
{errorMessage && (
@@ -173,11 +173,14 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
)}
</div>
</WithStep>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogFooter className="mt-8 sm:flex sm:flex-row-reverse" showDivider>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<Button
type="submit"
className="ms-2 me-2"
className="me-2 ms-2"
onClick={handleSetup}
disabled={password.length === 0 || isSubmitting}>
{t("continue")}
@@ -187,20 +190,17 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
<Button
type="submit"
data-testid="goto-otp-screen"
className="ms-2 me-2"
className="me-2 ms-2"
onClick={() => setStep(SetupStep.EnterTotpCode)}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<Button type="submit" className="ms-2 me-2" data-testid="enable-2fa" disabled={isSubmitting}>
<Button type="submit" className="me-2 ms-2" data-testid="enable-2fa" disabled={isSubmitting}>
{t("enable")}
</Button>
</WithStep>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</div>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
+1 -1
View File
@@ -111,7 +111,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
<Label htmlFor="username" className={classNames(longWebsiteUrl && "mb-0")}>
<span className="block">{t("username")}</span>
{longWebsiteUrl && (
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 py-1 px-3">
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 px-3 py-1">
{process.env.NEXT_PUBLIC_WEBSITE_URL}
</small>
)}
+1 -1
View File
@@ -27,7 +27,7 @@ const StepDone = (props: {
<Check className="text-inverted dark:bg-default dark:text-default inline-block h-10 w-10" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
<h2 className="mb-1 mt-6 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
</div>
</div>
</form>
+2 -2
View File
@@ -30,9 +30,9 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
alt={member.name || ""}
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
/>
<section className="line-clamp-4 mt-2 w-full space-y-1">
<section className="mt-2 line-clamp-4 w-full space-y-1">
<p className="text-default font-medium">{member.name}</p>
<div className="line-clamp-3 text-subtle overflow-ellipsis text-sm font-normal">
<div className="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
{!isBioEmpty ? (
<>
<div
+2 -2
View File
@@ -23,7 +23,7 @@ const EditableHeading = function EditableHeading({
<label className="min-w-8 relative inline-block">
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value}&nbsp;</span>
{!isEditing && isReady && (
<Edit2 className=" text-subtle group-hover:text-subtle ml-1 -mt-px inline h-3 w-3" />
<Edit2 className=" text-subtle group-hover:text-subtle -mt-px ml-1 inline h-3 w-3" />
)}
<input
{...passThroughProps}
@@ -31,7 +31,7 @@ const EditableHeading = function EditableHeading({
value={value}
required
className={classNames(
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
"text-emphasis hover:text-default focus:text-emphasis absolute left-0 top-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
)}
onFocus={(e) => {
setIsEditing(true);
+1 -1
View File
@@ -6,7 +6,7 @@ export default function InfoBadge({ content }: { content: string }) {
<>
<Tooltip side="top" content={content}>
<span title={content}>
<Info className="text-subtle relative top-px left-1 right-1 mt-px h-4 w-4" />
<Info className="text-subtle relative left-1 right-1 top-px mt-px h-4 w-4" />
</span>
</Tooltip>
</>
+1 -1
View File
@@ -14,7 +14,7 @@ export default function ModalContainer(
}>
) {
return (
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
<Dialog open={props.isOpen} onOpenChange={props.onExit}>
<DialogContent>
<div
@@ -224,7 +224,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
}}
data-testid="username-input"
/>
<div className="absolute top-0 right-2 flex flex-row">
<div className="absolute right-2 top-0 flex flex-row">
<span
className={classNames(
"mx-2 py-2",
@@ -80,7 +80,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
const ActionButtons = () => {
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
<div className="ms-2 me-2 flex flex-row space-x-2">
<div className="me-2 ms-2 flex flex-row space-x-2">
<Button
type="button"
onClick={() => setOpenDialogSaveUsername(true)}
@@ -24,9 +24,9 @@ interface UsernameAvailabilityFieldProps {
function useUserNamePrefix(organization: RouterOutputs["viewer"]["me"]["organization"]): string {
return organization
? organization.slug
? `${organization.slug}.${subdomainSuffix()}`
? `${organization.slug}.${subdomainSuffix()}/`
: organization.metadata && organization.metadata.requestedSlug
? `${organization.metadata.requestedSlug}.${subdomainSuffix()}`
? `${organization.metadata.requestedSlug}.${subdomainSuffix()}/`
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "");
}
+15
View File
@@ -0,0 +1,15 @@
const getDefaultSubdomain = (url) => {
if (!url.startsWith("http:") && !url.startsWith("https:")) {
// Make it a valid URL. Mabe we can simply return null and opt-out from orgs support till the use a URL scheme.
url = `https://${url}`;
}
const _url = new URL(url);
const regex = new RegExp(/^([a-z]+\:\/{2})?((?<subdomain>[\w-.]+)\.[\w-]+\.\w+)$/);
//console.log(_url.hostname, _url.hostname.match(regex));
return _url.hostname.match(regex)?.groups?.subdomain || null;
};
exports.getSubdomainRegExp = (url) => {
const defaultSubdomain = getDefaultSubdomain(url);
const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+";
return subdomain;
};
+2 -1
View File
@@ -5,7 +5,8 @@ import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import type { Maybe } from "@calcom/trpc/server";
import { i18n } from "../../../next-i18next.config";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { i18n } = require("@calcom/config/next-i18next.config");
export function getLocaleFromHeaders(req: IncomingMessage): string {
let preferredLocale: string | null | undefined;
-30
View File
@@ -3,7 +3,6 @@ import { collectEvents } from "next-collect/server";
import type { NextMiddleware } from "next/server";
import { NextResponse, userAgent } from "next/server";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { isIpInBanlist } from "@calcom/lib/getIP";
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
@@ -11,20 +10,11 @@ import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry
const middleware: NextMiddleware = async (req) => {
const url = req.nextUrl;
const requestHeaders = new Headers(req.headers);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.get("host") ?? "");
/**
* We are using env variable to toggle new-booker because using flags would be an unnecessary delay for booking pages
* Also, we can't easily identify the booker page requests here(to just fetch the flags for those requests)
*/
// Make sure we are in the presence of an organization
if (isValidOrgDomain && url.pathname === "/") {
// In the presence of an organization, cover its profile page at "/"
// rewrites for org profile page using team profile page
url.pathname = `/org/${currentOrgDomain}`;
return NextResponse.rewrite(url);
}
if (isIpInBanlist(req) && url.pathname !== "/api/nope") {
// DDOS Prevention: Immediately end request with no response - Avoids a redirect as well initiated by NextAuth on invalid callback
req.nextUrl.pathname = "/api/nope";
@@ -83,26 +73,6 @@ const middleware: NextMiddleware = async (req) => {
requestHeaders.set("x-csp-enforce", "true");
}
if (isValidOrgDomain) {
// Match /:slug to determine if it corresponds to org subteam slug or org user slug
const slugs = /^\/([^/]+)(\/[^/]+)?$/.exec(url.pathname);
// In the presence of an organization, if not team profile, a user or team is being accessed
if (slugs) {
const [_, teamName, eventType] = slugs;
// Fetch the corresponding subteams for the entered organization
const getSubteams = await fetch(`${WEBAPP_URL}/api/organizations/${currentOrgDomain}/subteams`);
if (getSubteams.ok) {
const data = await getSubteams.json();
// Treat entered slug as a team if found in the subteams fetched
if (data.slugs.includes(teamName)) {
// Rewriting towards /team/:slug to bring up the team profile within the org
url.pathname = `/team/${teamName}${eventType ?? ""}`;
return NextResponse.rewrite(url);
}
}
}
}
return NextResponse.next({
request: {
headers: requestHeaders,
+120 -28
View File
@@ -1,10 +1,11 @@
require("dotenv").config({ path: "../../.env" });
const CopyWebpackPlugin = require("copy-webpack-plugin");
const os = require("os");
const glob = require("glob");
const englishTranslation = require("./public/static/locales/en/common.json");
const { withAxiom } = require("next-axiom");
const { i18n } = require("./next-i18next.config");
const { pages } = require("./pages");
const { getSubdomainRegExp } = require("./getSubdomainRegExp");
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
@@ -70,7 +71,6 @@ const informAboutDuplicateTranslations = () => {
};
informAboutDuplicateTranslations();
const plugins = [];
if (process.env.ANALYZE === "true") {
// only load dependency if env `ANALYZE` was set
@@ -82,16 +82,52 @@ if (process.env.ANALYZE === "true") {
plugins.push(withAxiom);
/** Needed to rewrite public booking page, gets all static pages but [user] */
const pages = glob
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
.map((filename) =>
filename
.substr(6)
.replace(/(\.tsx|\.js|\.ts)/, "")
.replace(/\/.*/, "")
)
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
// It would also not match /free/30min/embed because we are ensuring just two slashes
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
// book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked
// Important Note: When modifying these RegExps update apps/web/test/lib/next-config.test.ts as well
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
const teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
const privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
const embedUserTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type/embed`;
const embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
const subdomainRegExp = getSubdomainRegExp(process.env.NEXT_PUBLIC_WEBAPP_URL);
// Important Note: Do update the RegExp in apps/web/test/lib/next-config.test.ts when changing it.
const orgHostRegExp = `^(?<orgSlug>${subdomainRegExp})\\..*`;
const matcherConfigRootPath = {
has: [
{
type: "host",
value: orgHostRegExp,
},
],
source: "/",
};
const matcherConfigOrgMemberPath = {
has: [
{
type: "host",
value: orgHostRegExp,
},
],
source: `/:user((?!${pages.join("|")}|_next|public)[a-zA-Z0-9\-_]+)`,
};
const matcherConfigUserPath = {
has: [
{
type: "host",
value: `^(?<orgSlug>${subdomainRegExp}[^.]+)\\..*`,
},
],
source: `/:user((?!${pages.join("|")}|_next|public))/:path*`,
};
/** @type {import("next").NextConfig} */
const nextConfig = {
@@ -183,12 +219,26 @@ const nextConfig = {
return config;
},
async rewrites() {
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
// It would also not match /free/30min/embed because we are ensuring just two slashes
const userTypeRouteRegExp = `/:user((?!${pages.join("|")})[^/]*)/:type`;
const beforeFiles = [
...(process.env.ORGANIZATIONS_ENABLED
? [
{
...matcherConfigRootPath,
destination: "/team/:orgSlug",
},
{
...matcherConfigOrgMemberPath,
destination: "/org/:orgSlug/:user",
},
{
...matcherConfigUserPath,
destination: "/:user/:path*",
},
]
: []),
];
let rewrites = [
let afterFiles = [
{
source: "/org/:slug",
destination: "/team/:slug",
@@ -232,12 +282,12 @@ const nextConfig = {
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/team/:slug/:type",
source: teamTypeRouteRegExp,
destination: "/new-booker/team/:slug/:type",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/d/:link/:slug",
source: privateLinkRouteRegExp,
destination: "/new-booker/d/:link/:slug",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
@@ -245,12 +295,12 @@ const nextConfig = {
// Keep cookie based booker enabled to test new-booker embed in production
...[
{
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
source: embedUserTypeRouteRegExp,
destination: "/new-booker/:user/:type/embed",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/team/:slug/:type/embed",
source: embedTeamTypeRouteRegExp,
destination: "/new-booker/team/:slug/:type/embed",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
@@ -268,14 +318,14 @@ const nextConfig = {
// Enable New Booker for all Embed Requests
if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") {
console.log("Enabling New Booker for Embed");
rewrites.push(
afterFiles.push(
...[
{
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
source: embedUserTypeRouteRegExp,
destination: "/new-booker/:user/:type/embed",
},
{
source: "/team/:slug/:type/embed",
source: embedTeamTypeRouteRegExp,
destination: "/new-booker/team/:slug/:type/embed",
},
]
@@ -285,24 +335,27 @@ const nextConfig = {
// Enable New Booker for All but embed Requests
if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") {
console.log("Enabling New Booker for Non-Embed");
rewrites.push(
afterFiles.push(
...[
{
source: userTypeRouteRegExp,
destination: "/new-booker/:user/:type",
},
{
source: "/team/:slug/:type",
source: teamTypeRouteRegExp,
destination: "/new-booker/team/:slug/:type",
},
{
source: "/d/:link/:slug",
source: privateLinkRouteRegExp,
destination: "/new-booker/d/:link/:slug",
},
]
);
}
return rewrites;
return {
beforeFiles,
afterFiles,
};
},
async headers() {
return [
@@ -337,6 +390,35 @@ const nextConfig = {
},
],
},
...[
{
...matcherConfigRootPath,
headers: [
{
key: "X-Cal-Org-path",
value: "/team/:orgSlug",
},
],
},
{
...matcherConfigOrgMemberPath,
headers: [
{
key: "X-Cal-Org-path",
value: "/org/:orgSlug/:user",
},
],
},
{
...matcherConfigUserPath,
headers: [
{
key: "X-Cal-Org-path",
value: "/:user/:path",
},
],
},
],
];
},
async redirects() {
@@ -411,6 +493,16 @@ const nextConfig = {
destination: "/event-types?openIntercom=true",
permanent: true,
},
{
source: "/apps/categories/video",
destination: "/apps/categories/conferencing",
permanent: true,
},
{
source: "/apps/installed/video",
destination: "/apps/installed/conferencing",
permanent: true,
},
];
if (process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com") {
+7 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.0.2",
"version": "3.0.10",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -48,7 +48,7 @@
"@radix-ui/react-avatar": "^1.0.0",
"@radix-ui/react-collapsible": "^1.0.0",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-id": "^1.0.0",
"@radix-ui/react-popover": "^1.0.2",
"@radix-ui/react-radio-group": "^1.0.0",
@@ -88,12 +88,12 @@
"memory-cache": "^0.2.0",
"micro": "^10.0.1",
"mime-types": "^2.1.35",
"next": "^13.2.1",
"next": "^13.4.6",
"next-auth": "^4.20.1",
"next-axiom": "^0.16.0",
"next-axiom": "^0.17.0",
"next-collect": "^0.2.1",
"next-i18next": "^11.3.0",
"next-seo": "^4.26.0",
"next-i18next": "^13.2.2",
"next-seo": "^6.0.0",
"next-themes": "^0.2.0",
"nodemailer": "^6.7.8",
"otplib": "^12.0.1",
@@ -109,7 +109,7 @@
"react-hook-form": "^7.43.3",
"react-hot-toast": "^2.3.0",
"react-intl": "^5.25.1",
"react-live-chat-loader": "^2.7.3",
"react-live-chat-loader": "^2.8.1",
"react-multi-email": "^0.5.3",
"react-phone-input-2": "^2.15.1",
"react-phone-number-input": "^3.2.7",
@@ -162,7 +162,6 @@
"copy-webpack-plugin": "^11.0.0",
"detect-port": "^1.3.0",
"env-cmd": "^10.1.0",
"mockdate": "^3.0.5",
"module-alias": "^2.2.2",
"msw": "^0.42.3",
"postcss": "^8.4.18",
+14
View File
@@ -0,0 +1,14 @@
const glob = require("glob");
/** Needed to rewrite public booking page, gets all static pages but [user] */
const pages = glob
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
.map((filename) =>
filename
.substr(6)
.replace(/(\.tsx|\.js|\.ts)/, "")
.replace(/\/.*/, "")
)
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
exports.pages = pages;
+91 -43
View File
@@ -3,22 +3,37 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { orgDomainConfig, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HeadSeo } from "@calcom/ui";
import { BookOpen, Check, ChevronRight, FileText, Slack } from "@calcom/ui/components/icon";
import { BookOpen, Check, ChevronRight, FileText, Slack, Shield } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import { ssgInit } from "@server/lib/ssg";
enum pageType {
ORG = "org",
TEAM = "team",
USER = "user",
OTHER = "other",
}
export default function Custom404() {
const { t } = useLocale();
const router = useRouter();
const [username] = router.asPath.replace("%20", "-").split(/[?#]/);
const [username, setUsername] = useState<string>("");
const [currentPageType, setCurrentPageType] = useState<pageType>(pageType.USER);
const links = [
{
title: "Enterprise",
description: "Learn more about organizations and subdomains in our enterprise plan.",
icon: Shield,
href: `${WEBSITE_URL}/enterprise`,
},
{
title: t("documentation"),
description: t("documentation_description"),
@@ -33,10 +48,29 @@ export default function Custom404() {
},
];
const [url, setUrl] = useState(`${WEBSITE_URL}/signup?username=`);
const [url, setUrl] = useState(`${WEBSITE_URL}/signup`);
useEffect(() => {
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
}, [username]);
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(window.location.host);
const [routerUsername] = router.asPath.replace("%20", "-").split(/[?#]/);
if (!isValidOrgDomain || !currentOrgDomain) {
const splitPath = routerUsername.split("/");
if (splitPath[1] === "team" && splitPath.length === 3) {
// Accessing a non-existent team
setUsername(splitPath[2]);
setCurrentPageType(pageType.TEAM);
setUrl(`${WEBSITE_URL}/signup?callbackUrl=settings/teams/new%3Fslug%3D${username.replace("/", "")}`);
} else {
setUsername(routerUsername);
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
}
} else {
setUsername(currentOrgDomain);
setCurrentPageType(pageType.ORG);
setUrl(
`${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${username.replace("/", "")}`
);
}
}, []);
const isSuccessPage = router.asPath.startsWith("/booking");
const isSubpage = router.asPath.includes("/", 2) || isSuccessPage;
@@ -59,7 +93,7 @@ export default function Custom404() {
}}
/>
<div className="min-h-screen bg-white px-4" data-testid="404-page">
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
<div className="text-center">
<p className="text-sm font-semibold uppercase tracking-wide text-black">{t("error_404")}</p>
<h1 className="font-cal mt-2 text-4xl font-extrabold text-gray-900 sm:text-5xl">
@@ -80,6 +114,8 @@ export default function Custom404() {
);
}
if (!username) return null;
return (
<>
<HeadSeo
@@ -91,7 +127,7 @@ export default function Custom404() {
}}
/>
<div className="bg-default min-h-screen px-4" data-testid="404-page">
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
{isSignup && process.env.NEXT_PUBLIC_WEBAPP_URL !== "https://app.cal.com" ? (
<div>
<div>
@@ -202,13 +238,13 @@ export default function Custom404() {
<h1 className="font-cal text-emphasis mt-2 text-4xl font-extrabold sm:text-5xl">
{isSuccessPage ? "Booking not found" : t("page_doesnt_exist")}
</h1>
{isSubpage ? (
{isSubpage && currentPageType !== pageType.TEAM ? (
<span className="mt-2 inline-block text-lg ">
{t("check_spelling_mistakes_or_go_back")}
</span>
) : isCalcom ? (
<a target="_blank" href={url} className="mt-2 inline-block text-lg" rel="noreferrer">
{t("the_username")}{" "}
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
<strong className="text-blue-500">
{new URL(WEBSITE_URL).hostname}
{username}
@@ -217,18 +253,17 @@ export default function Custom404() {
</a>
) : (
<span className="mt-2 inline-block text-lg">
{t("the_username")}{" "}
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
<strong className="text-lgtext-green-500 mt-2 inline-block">{username}</strong>{" "}
{t("is_still_available")}
</span>
)}
</div>
<div className="mt-12">
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
{t("popular_pages")}
</h2>
{!isSubpage && isCalcom && (
<ul role="list" className="mt-4">
{((!isSubpage && isCalcom) ||
currentPageType === pageType.ORG ||
currentPageType === pageType.TEAM) && (
<ul role="list" className="my-4">
<li className="border-2 border-green-500 px-4 py-2">
<a
href={url}
@@ -245,11 +280,20 @@ export default function Custom404() {
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
{t("register")} <strong className="text-green-500">{username}</strong>
{t("register")}{" "}
<strong className="text-green-500">{`${
currentPageType === pageType.TEAM
? `${new URL(WEBSITE_URL).host}/team/`
: ""
}${username}${
currentPageType === pageType.ORG ? `.${subdomainSuffix()}` : ""
}`}</strong>
</span>
</span>
</h3>
<p className="text-subtle text-base">{t("claim_username_and_schedule_events")}</p>
<p className="text-subtle text-base">
{t(`404_claim_entity_${currentPageType.toLowerCase()}`)}
</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
@@ -258,33 +302,37 @@ export default function Custom404() {
</li>
</ul>
)}
<ul role="list" className="border-subtle divide-subtle mt-4 divide-y">
{links.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2">
<Link
href={link.href}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-emphasis text-base font-medium">
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
{t("popular_pages")}
</h2>
<ul role="list" className="border-subtle divide-subtle divide-y">
{links
.filter((_, idx) => currentPageType === pageType.ORG || idx !== 0)
.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2">
<Link
href={link.href}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
</span>
</h3>
<p className="text-subtle text-base">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
</div>
</Link>
</li>
))}
</div>
<div className="min-w-0 flex-1">
<h3 className="text-emphasis text-base font-medium">
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span>
</h3>
<p className="text-subtle text-base">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
</div>
</Link>
</li>
))}
<li className="px-4 py-2">
<a
href={JOIN_SLACK}
+1 -1
View File
@@ -21,7 +21,7 @@ export default function Error500() {
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
<h1 className="font-cal text-emphasis text-6xl">500</h1>
<h2 className="text-emphasis mt-6 text-2xl font-medium">It&apos;s not you, it&apos;s us.</h2>
<p className="text-default mt-4 mb-6 max-w-2xl text-sm">
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right
away for you.
</p>
+9 -4
View File
@@ -37,7 +37,8 @@ import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
export type UserPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export function UserPage(props: UserPageProps) {
const {
users,
profile,
@@ -102,6 +103,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const query = { ...router.query };
delete query.user; // So it doesn't display in the Link (and make tests fail)
delete query.orgSlug;
const nameOrUsername = user.name || user.username || "";
/*
@@ -132,7 +134,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
"max-w-3xl py-24 px-4"
"max-w-3xl px-4 py-24"
)}>
{isSingleUser && ( // When we deal with a single user, not dynamic group
<div className="mb-8 text-center">
@@ -207,8 +209,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
);
}
User.isBookingPage = true;
User.PageWrapper = PageWrapper;
UserPage.isBookingPage = true;
UserPage.PageWrapper = PageWrapper;
const getEventTypesWithHiddenFromDB = async (userId: number) => {
return (
@@ -360,6 +362,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const safeBio = markdownToSafeHTML(user.bio) || "";
const markdownStrippedBio = stripMarkdown(user?.bio || "");
return {
props: {
users,
@@ -385,3 +388,5 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
};
};
export default UserPage;
@@ -1 +0,0 @@
export { getStaticProps, getStaticPaths, default } from "./index";
@@ -1,69 +0,0 @@
/**
* This page is empty for the user, it is used only to take advantage of the
* caching system that NextJS uses SSG pages.
* TODO: Redirect to user profile on browser
*/
import type { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next";
import { z } from "zod";
import getCalendarsEvents from "@calcom/core/getCalendarsEvents";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
const paramsSchema = z.object({ user: z.string(), month: z.string() });
export const getStaticProps: GetStaticProps<
{ results: Awaited<ReturnType<typeof getCalendarsEvents>> },
{ user: string }
> = async (context) => {
const { user: username, month } = paramsSchema.parse(context.params);
const userWithCredentials = await prisma.user.findFirst({
where: {
username,
},
select: {
id: true,
username: true,
credentials: true,
selectedCalendars: true,
},
});
// Subtract 11 hours from the start date to avoid problems in UTC- time zones.
const startDate = dayjs.utc(month, "YYYY-MM").startOf("day").subtract(11, "hours").format();
// Add 14 hours from the start date to avoid problems in UTC+ time zones.
const endDate = dayjs.utc(month, "YYYY-MM").endOf("month").add(14, "hours").format();
try {
const results = userWithCredentials?.credentials
? await getCalendarsEvents(
userWithCredentials?.credentials,
startDate,
endDate,
userWithCredentials?.selectedCalendars
)
: [];
return {
props: { results, date: new Date().toISOString() },
revalidate: 1,
};
} catch (error) {
let message = "Unknown error while fetching calendar";
if (error instanceof Error) message = error.message;
console.error(error, message);
return {
props: { results: [], date: new Date().toISOString(), message },
revalidate: 1,
};
}
};
export const getStaticPaths: GetStaticPaths = () => {
return {
paths: [],
fallback: "blocking",
};
};
type Props = InferGetStaticPropsType<typeof getStaticProps>;
const CalendarCache = (props: Props) =>
process.env.NODE_ENV === "development" ? <pre>{JSON.stringify(props, null, " ")}</pre> : <div />;
export default CalendarCache;
+3 -8
View File
@@ -5,8 +5,7 @@ import { z } from "zod";
import dayjs from "@calcom/dayjs";
import { sendPasswordResetEmail } from "@calcom/emails";
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import rateLimiter from "@calcom/lib/rateLimit";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { defaultHandler } from "@calcom/lib/server";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma from "@calcom/prisma";
@@ -34,15 +33,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// 10 requests per minute
const limiter = await rateLimiter();
const limit = await limiter({
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: ip,
});
if (!limit.success) {
throw new Error(ErrorCode.RateLimitExceeded);
}
try {
const maybeUser = await prisma.user.findUnique({
where: {
+91 -13
View File
@@ -1,18 +1,21 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import dayjs from "@calcom/dayjs";
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import slugify from "@calcom/lib/slugify";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
const signupSchema = z.object({
username: z.string(),
email: z.string().email(),
password: z.string().min(7),
language: z.string().optional(),
token: z.string().optional(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -26,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
const data = req.body;
const { email, password, language } = signupSchema.parse(data);
const { email, password, language, token } = signupSchema.parse(data);
const username = slugify(data.username);
const userEmail = email.toLowerCase();
@@ -85,25 +88,100 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
// If user has been invitedTo a team, we accept the membership
if (user.invitedTo) {
const team = await prisma.team.findFirst({
where: { id: user.invitedTo },
if (token) {
const foundToken = await prisma.verificationToken.findFirst({
where: {
token,
},
});
if (team) {
const membership = await prisma.membership.update({
if (!foundToken) {
return res.status(401).json({ message: "Invalid Token" });
}
if (dayjs(foundToken?.expires).isBefore(dayjs())) {
return res.status(401).json({ message: "Token expired" });
}
if (foundToken.teamId) {
const team = await prisma.team.findUnique({
where: {
userId_teamId: { userId: user.id, teamId: user.invitedTo },
},
data: {
accepted: true,
id: foundToken.teamId,
},
});
// Sync Services: Close.com
closeComUpsertTeamUser(team, user, membership.role);
if (team) {
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
if (teamMetadata?.isOrganization) {
await prisma.user.update({
where: {
id: user.id,
},
data: {
organizationId: team.id,
},
});
}
const membership = await prisma.membership.update({
where: {
userId_teamId: { userId: user.id, teamId: team.id },
},
data: {
accepted: true,
},
});
closeComUpsertTeamUser(team, user, membership.role);
// Accept any child team invites for orgs.
if (team.parentId) {
// Join ORG
await prisma.user.update({
where: {
id: user.id,
},
data: {
organizationId: team.parentId,
},
});
/** We do a membership update twice so we can join the ORG invite if the user is invited to a team witin a ORG. */
await prisma.membership.updateMany({
where: {
userId: user.id,
team: {
id: team.parentId,
},
accepted: false,
},
data: {
accepted: true,
},
});
// Join any other invites
await prisma.membership.updateMany({
where: {
userId: user.id,
team: {
parentId: team.parentId,
},
accepted: false,
},
data: {
accepted: true,
},
});
}
}
}
// Cleanup token after use
await prisma.verificationToken.delete({
where: {
id: foundToken.id,
},
});
}
await sendEmailVerification({
+14 -11
View File
@@ -1,11 +1,16 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import notEmpty from "@calcom/lib/notEmpty";
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
import prisma from "@calcom/prisma";
const selectedCalendarSelectSchema = z.object({
integration: z.string(),
externalId: z.string(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getServerSession({ req, res });
@@ -32,18 +37,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { credentials, ...user } = userWithCredentials;
if (req.method === "POST") {
const { integration, externalId } = selectedCalendarSelectSchema.parse(req.body);
await prisma.selectedCalendar.upsert({
where: {
userId_integration_externalId: {
userId: user.id,
integration: req.body.integration,
externalId: req.body.externalId,
integration,
externalId,
},
},
create: {
userId: user.id,
integration: req.body.integration,
externalId: req.body.externalId,
integration,
externalId,
},
// already exists
update: {},
@@ -52,12 +58,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
if (req.method === "DELETE") {
const { integration, externalId } = selectedCalendarSelectSchema.parse(req.query);
await prisma.selectedCalendar.delete({
where: {
userId_integration_externalId: {
userId: user.id,
externalId: req.body.externalId,
integration: req.body.integration,
externalId,
integration,
},
},
});
@@ -65,10 +72,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
res.status(200).json({ message: "Calendar Selection Saved" });
}
if (["DELETE", "POST"].includes(req.method)) {
await revalidateCalendarCache(res.revalidate, `${session?.user?.username}`);
}
if (req.method === "GET") {
const selectedCalendarIds = await prisma.selectedCalendar.findMany({
where: {
+67
View File
@@ -0,0 +1,67 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import logger from "@calcom/lib/logger";
import { prisma } from "@calcom/prisma";
import type { AppCategories, Prisma } from "@calcom/prisma/client";
const isDryRun = process.env.CRON_ENABLE_APP_SYNC !== "true";
const log = logger.getChildLogger({
prefix: ["[api/cron/syncAppMeta]", ...(isDryRun ? ["(dry-run)"] : [])],
});
/**
* syncAppMeta makes sure any app metadata that has been replicated into the database
* remains synchronized with any changes made to the app config files.
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.headers.authorization || req.query.apiKey;
if (process.env.CRON_API_KEY !== apiKey) {
res.status(401).json({ message: "Not authenticated" });
return;
}
if (req.method !== "POST") {
res.status(405).json({ message: "Invalid method" });
return;
}
log.info(`🧐 Checking DB apps are in-sync with app metadata`);
const dbApps = await prisma.app.findMany();
for await (const dbApp of dbApps) {
const app = await getAppWithMetadata(dbApp);
const updates: Prisma.AppUpdateManyMutationInput = {};
if (!app) {
log.warn(`💀 App ${dbApp.slug} (${dbApp.dirName}) no longer exists.`);
continue;
}
// Check for any changes in the app categories (tolerates changes in ordering)
if (
dbApp.categories.length !== app.categories.length ||
!dbApp.categories.every((category) => app.categories.includes(category))
) {
updates["categories"] = app.categories as AppCategories[];
}
if (dbApp.dirName !== (app.dirName ?? app.slug)) {
updates["dirName"] = app.dirName ?? app.slug;
}
if (Object.keys(updates).length > 0) {
log.info(`🔨 Updating app ${dbApp.slug} with ${Object.keys(updates).join(", ")}`);
if (!isDryRun) {
await prisma.app.update({
where: { slug: dbApp.slug },
data: updates,
});
}
} else {
log.info(`✅ App ${dbApp.slug} is up-to-date and correct`);
}
}
res.json({ ok: true });
}
@@ -5,7 +5,6 @@ import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import { HttpError } from "@calcom/lib/http-error";
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
import prisma from "@calcom/prisma";
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
@@ -62,14 +61,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (typeof handler === "function") {
await handler(req, res);
if (appName.includes("calendar") && req.session?.user?.username) {
await revalidateCalendarCache(res.revalidate, req.session?.user?.username);
}
} else {
await defaultIntegrationAddHandler({ user: req.session?.user, ...handler });
if (handler.appType.includes("calendar") && req.session?.user?.username) {
await revalidateCalendarCache(res.revalidate, req.session?.user?.username);
}
redirectUrl = handler.redirect?.url || getInstalledAppPath(handler);
res.json({ url: redirectUrl, newTab: handler.redirect?.newTab });
}
@@ -1,27 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
const querySchema = z.object({
username: z.string(),
});
/**
* This endpoint revalidates users calendar cache several months ahead
* Can be used as webhook
* @param req
* @param res
* @returns
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { username } = querySchema.parse(req.query);
try {
await revalidateCalendarCache(res.revalidate, username);
return res.status(200).json({ revalidated: true });
} catch (err) {
return res.status(500).send({ message: "Error revalidating" });
}
}
+20 -16
View File
@@ -6,11 +6,11 @@ import { AppSettings } from "@calcom/app-store/_components/AppSettings";
import { InstallAppButton } from "@calcom/app-store/components";
import type { EventLocationType } from "@calcom/app-store/locations";
import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
import { InstalledAppVariants } from "@calcom/app-store/utils";
import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
import { BulkEditDefaultConferencingModal } from "@calcom/features/eventtypes/components/BulkEditDefaultConferencingModal";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { AppCategories } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
@@ -29,11 +29,14 @@ import {
DropdownItem,
showToast,
} from "@calcom/ui";
import type { LucideIcon } from "@calcom/ui/components/icon";
import {
BarChart,
Calendar,
Contact,
CreditCard,
Grid,
Mail,
MoreHorizontal,
Plus,
Share2,
@@ -101,8 +104,8 @@ function ConnectOrDisconnectIntegrationMenuItem(props: {
}
interface IntegrationsContainerProps {
variant?: (typeof InstalledAppVariants)[number];
exclude?: (typeof InstalledAppVariants)[number][];
variant?: AppCategories;
exclude?: AppCategories[];
handleDisconnect: (credentialId: number) => void;
}
@@ -225,14 +228,19 @@ const IntegrationsContainer = ({
}: IntegrationsContainerProps): JSX.Element => {
const { t } = useLocale();
const query = trpc.viewer.integrations.useQuery({ variant, exclude, onlyInstalled: true });
const emptyIcon = {
// TODO: Refactor and reuse getAppCategories?
const emptyIcon: Record<AppCategories, LucideIcon> = {
calendar: Calendar,
conferencing: Video,
automation: Share2,
analytics: BarChart,
payment: CreditCard,
web3: BarChart,
web3: BarChart, // deprecated
other: Grid,
video: Video, // deprecated
messaging: Mail,
crm: Contact,
};
return (
@@ -267,9 +275,7 @@ const IntegrationsContainer = ({
className="mb-6"
actions={
<Button
href={
variant ? `/apps/categories/${variant === "conferencing" ? "video" : variant}` : "/apps"
}
href={variant ? `/apps/categories/${variant}` : "/apps"}
color="secondary"
StartIcon={Plus}>
{t("add")}
@@ -285,7 +291,7 @@ const IntegrationsContainer = ({
};
const querySchema = z.object({
category: z.enum(InstalledAppVariants),
category: z.nativeEnum(AppCategories),
});
type querySchemaType = z.infer<typeof querySchema>;
@@ -299,13 +305,11 @@ export default function InstalledApps() {
const { t } = useLocale();
const router = useRouter();
const category = router.query.category as querySchemaType["category"];
const categoryList: querySchemaType["category"][] = [
"payment",
"conferencing",
"automation",
"analytics",
"web3",
];
const categoryList: AppCategories[] = Object.values(AppCategories).filter((category) => {
// Exclude calendar and other from categoryList, we handle those slightly differently below
return !(category in { other: null, calendar: null });
});
const [data, updateData] = useReducer(
(data: ModalState, partialData: Partial<ModalState>) => ({ ...data, ...partialData }),
+2 -1
View File
@@ -1,6 +1,7 @@
import type { GetStaticPropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import type { ReactElement } from "react";
import z from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -21,7 +22,7 @@ export default function Error() {
const router = useRouter();
const { error } = querySchema.parse(router.query);
const isTokenVerificationError = error?.toLowerCase() === "verification";
let errorMsg = <SkeletonText />;
let errorMsg: string | ReactElement = <SkeletonText />;
if (router.isReady) {
errorMsg = isTokenVerificationError ? t("token_invalid_expired") : t("error_during_login");
}
+14 -8
View File
@@ -189,7 +189,11 @@ export default function Availability() {
CTA={
<div className="flex items-center justify-end">
<div className="sm:hover:bg-muted hidden items-center rounded-md px-2 sm:flex">
<Skeleton as={Label} htmlFor="hiddenSwitch" className="mt-2 cursor-pointer self-center pr-2 ">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
className="mt-2 cursor-pointer self-center pe-2"
loadingClassName="me-4">
{t("set_to_default")}
</Skeleton>
<Switch
@@ -254,7 +258,7 @@ export default function Availability() {
</ConfirmationDialogContent>
</Dialog>
<DropdownMenuSeparator />
<div className="flex h-9 flex-row items-center justify-between py-2 px-4 hover:bg-gray-100">
<div className="flex h-9 flex-row items-center justify-between px-4 py-2 hover:bg-gray-100">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
@@ -315,9 +319,9 @@ export default function Availability() {
<div className="min-w-40 col-span-3 space-y-2 lg:col-span-1">
<div className="xl:max-w-80 w-full pr-4 sm:ml-0 sm:mr-36 sm:p-0">
<div>
<label htmlFor="timeZone" className="text-default block text-sm font-medium">
<Skeleton as={Label} htmlFor="timeZone" className="mb-0 inline-block leading-none">
{t("timezone")}
</label>
</Skeleton>
<Controller
name="timeZone"
render={({ field: { onChange, value } }) =>
@@ -328,18 +332,20 @@ export default function Availability() {
onChange={(timezone) => onChange(timezone.value)}
/>
) : (
<SelectSkeletonLoader className="w-72" />
<SelectSkeletonLoader className="mt-1 w-72" />
)
}
/>
</div>
<hr className="border-subtle my-6 mr-8" />
<div className="hidden rounded-md md:block">
<h3 className="text-emphasis text-sm font-medium">{t("something_doesnt_look_right")}</h3>
<Skeleton as="h3" className="mb-0 inline-block text-sm font-medium">
{t("something_doesnt_look_right")}
</Skeleton>
<div className="mt-3 flex">
<Button href="/availability/troubleshoot" color="secondary">
<Skeleton as={Button} href="/availability/troubleshoot" color="secondary">
{t("launch_troubleshooter")}
</Button>
</Skeleton>
</div>
</div>
</div>
+17
View File
@@ -1,4 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useRouter } from "next/router";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import Shell from "@calcom/features/shell/Shell";
@@ -20,6 +21,8 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
const meQuery = trpc.viewer.me.useQuery();
const router = useRouter();
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
onMutate: async ({ scheduleId }) => {
await utils.viewer.availability.list.cancel();
@@ -67,6 +70,19 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
},
});
const duplicateMutation = trpc.viewer.availability.schedule.duplicate.useMutation({
onSuccess: async ({ schedule }) => {
await router.push(`/availability/${schedule.id}`);
showToast(t("schedule_created_successfully", { scheduleName: schedule.name }), "success");
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
});
// Adds smooth delete button - item fades and old item slides into place
const [animationParentRef] = useAutoAnimate<HTMLUListElement>();
@@ -96,6 +112,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
isDeletable={schedules.length !== 1}
updateDefault={updateMutation.mutate}
deleteFunction={deleteMutation.mutate}
duplicateFunction={duplicateMutation.mutate}
/>
))}
</ul>
+34 -9
View File
@@ -49,8 +49,9 @@ import type { Prisma } from "@calcom/prisma/client";
import { BookingStatus } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme } from "@calcom/ui";
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme, Alert } from "@calcom/ui";
import { X, ExternalLink, ChevronLeft, Check, Calendar } from "@calcom/ui/components/icon";
import { AlertCircle } from "@calcom/ui/components/icon";
import { timeZone } from "@lib/clock";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -127,6 +128,10 @@ export default function Success(props: SuccessProps) {
? props?.bookingInfo?.attendees?.[0]?.name
: "Nameless";
const attendees = props?.bookingInfo?.attendees;
const isGmail = !!attendees.find((attendee) => attendee.email.includes("gmail.com"));
const [is24h, setIs24h] = useState(isBrowserLocale24h());
const { data: session } = useSession();
@@ -306,7 +311,7 @@ export default function Success(props: SuccessProps) {
/>
)}
{userIsOwner && !isEmbed && (
<div className="mt-2 ml-4 -mb-4">
<div className="-mb-4 ml-4 mt-2">
<Link
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
@@ -321,7 +326,7 @@ export default function Success(props: SuccessProps) {
<div
className={classNames(
shouldAlignCentrally ? "text-center" : "",
"flex items-end justify-center px-4 pt-4 pb-20 sm:block sm:p-0"
"flex items-end justify-center px-4 pb-20 pt-4 sm:block sm:p-0"
)}>
<div
className={classNames("my-4 transition-opacity sm:my-0", isEmbed ? "" : " inset-0")}
@@ -330,7 +335,7 @@ export default function Success(props: SuccessProps) {
className={classNames(
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
!isBackgroundTransparent && " bg-default dark:bg-muted border-booker border-booker-width",
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
"px-8 pb-4 pt-5 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
)}
role="dialog"
aria-modal="true"
@@ -356,7 +361,7 @@ export default function Success(props: SuccessProps) {
{needsConfirmation && !isCancelled && <Calendar className="text-emphasis h-5 w-5" />}
{isCancelled && <X className="h-5 w-5 text-red-600" />}
</div>
<div className="mt-6 mb-8 text-center last:mb-0">
<div className="mb-8 mt-6 text-center last:mb-0">
<h3
className="text-emphasis text-2xl font-semibold leading-6"
data-testid={isCancelled ? "cancelled-headline" : ""}
@@ -394,7 +399,7 @@ export default function Success(props: SuccessProps) {
{(isCancelled || reschedule) && cancellationReason && (
<>
<div className="font-medium">
{isCancelled ? t("reason") : t("reschedule_reason_success_page")}
{isCancelled ? t("reason") : t("reschedule_reason")}
</div>
<div className="col-span-2 mb-6 last:mb-0">{cancellationReason}</div>
</>
@@ -615,7 +620,7 @@ export default function Success(props: SuccessProps) {
className="border-subtle text-default mx-2 h-10 w-10 rounded-sm border px-3 py-2"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
className="-mt-1.5 mr-1 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
@@ -642,7 +647,7 @@ export default function Success(props: SuccessProps) {
className="text-default border-subtle mx-2 h-10 w-10 rounded-sm border px-3 py-2"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
className="-mt-1.5 mr-1 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
@@ -659,7 +664,7 @@ export default function Success(props: SuccessProps) {
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000"
className="mr-1 -mt-1.5 inline-block h-4 w-4">
className="-mt-1.5 mr-1 inline-block h-4 w-4">
<title>{t("other")}</title>
<path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" />
</svg>
@@ -705,6 +710,26 @@ export default function Success(props: SuccessProps) {
</>
)}
</div>
{isGmail && (
<Alert
className="main -mb-20 mt-4 inline-block text-left sm:-mt-4 sm:mb-4 sm:w-full sm:max-w-xl sm:align-middle"
severity="warning"
message={
<div>
<p className="font-semibold">{t("google_new_spam_policy")}</p>
<span className="underline">
<a
target="_blank"
href="https://cal.com/blog/google-s-new-spam-policy-may-be-affecting-your-invitations">
{t("resolve")}
</a>
</span>
</div>
}
CustomIcon={AlertCircle}
customIconColor="text-attention dark:text-orange-200"
/>
)}
</div>
</div>
</div>
+53 -53
View File
@@ -2,7 +2,7 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import type { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import dynamic from "next/dynamic";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -29,27 +29,54 @@ import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { IntervalLimit, RecurringEvent } from "@calcom/types/Calendar";
import { ConfirmationDialogContent, Dialog, Form, showToast } from "@calcom/ui";
import { Form, showToast } from "@calcom/ui";
import { asStringOrThrow } from "@lib/asStringOrNull";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
// These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings
import { EventAdvancedTab } from "@components/eventtype/EventAdvancedTab";
import { EventAppsTab } from "@components/eventtype/EventAppsTab";
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
import { EventAvailabilityTab } from "@components/eventtype/EventAvailabilityTab";
import { EventLimitsTab } from "@components/eventtype/EventLimitsTab";
import { EventRecurringTab } from "@components/eventtype/EventRecurringTab";
import { EventSetupTab } from "@components/eventtype/EventSetupTab";
import { EventTeamTab } from "@components/eventtype/EventTeamTab";
import { EventTypeSingleLayout } from "@components/eventtype/EventTypeSingleLayout";
import { EventWebhooksTab } from "@components/eventtype/EventWebhooksTab";
import EventWorkflowsTab from "@components/eventtype/EventWorkfowsTab";
import { ssrInit } from "@server/lib/ssr";
// These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings;
const EventSetupTab = dynamic(() =>
import("@components/eventtype/EventSetupTab").then((mod) => mod.EventSetupTab)
);
const EventAvailabilityTab = dynamic(() =>
import("@components/eventtype/EventAvailabilityTab").then((mod) => mod.EventAvailabilityTab)
);
const EventTeamTab = dynamic(() =>
import("@components/eventtype/EventTeamTab").then((mod) => mod.EventTeamTab)
);
const EventLimitsTab = dynamic(() =>
import("@components/eventtype/EventLimitsTab").then((mod) => mod.EventLimitsTab)
);
const EventAdvancedTab = dynamic(() =>
import("@components/eventtype/EventAdvancedTab").then((mod) => mod.EventAdvancedTab)
);
const EventRecurringTab = dynamic(() =>
import("@components/eventtype/EventRecurringTab").then((mod) => mod.EventRecurringTab)
);
const EventAppsTab = dynamic(() =>
import("@components/eventtype/EventAppsTab").then((mod) => mod.EventAppsTab)
);
const EventWorkflowsTab = dynamic(() => import("@components/eventtype/EventWorkfowsTab"));
const EventWebhooksTab = dynamic(() =>
import("@components/eventtype/EventWebhooksTab").then((mod) => mod.EventWebhooksTab)
);
const ManagedEventTypeDialog = dynamic(() => import("@components/eventtype/ManagedEventDialog"));
export type FormValues = {
title: string;
eventTitle: string;
@@ -168,18 +195,14 @@ const EventTypePage = (props: EventTypeSetupProps) => {
}
if (err.data?.code === "UNAUTHORIZED") {
message = `${err.data.code}: You are not able to update this event`;
message = `${err.data.code}: ${t("error_event_type_unauthorized_update")}`;
}
if (err.data?.code === "PARSE_ERROR" || err.data?.code === "BAD_REQUEST") {
message = `${err.data.code}: ${err.message}`;
message = `${err.data.code}: ${t(err.message)}`;
}
if (message) {
showToast(message, "error");
} else {
showToast(err.message, "error");
}
showToast(message ? t(message) : t(err.message), "error");
},
});
@@ -476,46 +499,23 @@ const EventTypePage = (props: EventTypeSetupProps) => {
<div ref={animationParentRef}>{tabMap[tabName]}</div>
</Form>
</EventTypeSingleLayout>
<Dialog
open={slugExistsChildrenDialogOpen.length > 0}
onOpenChange={() => {
setSlugExistsChildrenDialogOpen([]);
}}>
<ConfirmationDialogContent
{slugExistsChildrenDialogOpen.length && (
<ManagedEventTypeDialog
slugExistsChildrenDialogOpen={slugExistsChildrenDialogOpen}
isLoading={formMethods.formState.isSubmitting}
variety="warning"
title={t("managed_event_dialog_title", {
slug,
count: slugExistsChildrenDialogOpen.length,
})}
confirmBtnText={t("managed_event_dialog_confirm_button", {
count: slugExistsChildrenDialogOpen.length,
})}
cancelBtnText={t("go_back")}
onOpenChange={() => {
setSlugExistsChildrenDialogOpen([]);
}}
slug={slug}
onConfirm={(e: { preventDefault: () => void }) => {
e.preventDefault();
handleSubmit(formMethods.getValues());
telemetry.event(telemetryEventTypes.slugReplacementAction);
setSlugExistsChildrenDialogOpen([]);
}}>
<p className="mt-5">
<Trans
i18nKey="managed_event_dialog_information"
values={{
names: `${slugExistsChildrenDialogOpen
.map((ch) => ch.owner.name)
.slice(0, -1)
.join(", ")} ${
slugExistsChildrenDialogOpen.length > 1 ? t("and") : ""
} ${slugExistsChildrenDialogOpen.map((ch) => ch.owner.name).slice(-1)}`,
slug,
}}
count={slugExistsChildrenDialogOpen.length}
/>
</p>{" "}
<p className="mt-5">{t("managed_event_dialog_clarification")}</p>
</ConfirmationDialogContent>
</Dialog>
}}
/>
)}
</>
);
};
+63 -55
View File
@@ -13,7 +13,8 @@ import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
import { OrganizationEventTypeFilter } from "@calcom/features/eventtypes/components/OrganizationEventTypeFilter";
import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
import Shell from "@calcom/features/shell/Shell";
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -47,7 +48,6 @@ import {
HeadSeo,
Skeleton,
Label,
VerticalDivider,
Alert,
} from "@calcom/ui";
import {
@@ -383,7 +383,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<div className="group flex w-full max-w-full items-center justify-between overflow-hidden px-4 py-4 sm:px-6">
{!(firstItem && firstItem.id === type.id) && (
<button
className="bg-default text-muted hover:text-emphasis border-default hover:border-emphasis invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
className="bg-default text-muted hover:text-emphasis border-default hover:border-emphasis invisible absolute left-[5px] -ml-4 -mt-4 mb-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, -1)}>
<ArrowUp className="h-5 w-5" />
</button>
@@ -391,7 +391,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
{!(lastItem && lastItem.id === type.id) && (
<button
className="bg-default text-muted border-default hover:text-emphasis hover:border-emphasis invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
className="bg-default text-muted border-default hover:text-emphasis hover:border-emphasis invisible absolute left-[5px] -ml-4 mt-8 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, 1)}>
<ArrowDown className="h-5 w-5" />
</button>
@@ -401,7 +401,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
{type.team && !isManagedEventType && (
<AvatarGroup
className="relative top-1 right-3"
className="relative right-3 top-1"
size="sm"
truncateAfter={4}
items={type.users.map(
@@ -415,12 +415,12 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
)}
{isManagedEventType && (
<AvatarGroup
className="relative top-1 right-3"
className="relative right-3 top-1"
size="sm"
truncateAfter={4}
items={type.children
.flatMap((ch) => ch.users)
.map((user: User) => ({
.map((user: Pick<User, "name" | "username">) => ({
alt: user.name || "",
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
title: user.name || "",
@@ -640,7 +640,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
)}
<DropdownMenuSeparator />
{!isManagedEventType && (
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between px-4 py-2">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
@@ -728,10 +728,10 @@ const EventTypeListHeading = ({
{profile?.name || ""}
</Link>
{membershipCount && teamId && (
<span className="text-subtle ms-2 me-2 relative -top-px text-xs">
<span className="text-subtle relative -top-px me-2 ms-2 text-xs">
<Link href={`/settings/teams/${teamId}/members`}>
<Badge variant="gray">
<Users className="mr-1 -mt-px inline h-3 w-3" />
<Users className="-mt-px mr-1 inline h-3 w-3" />
{membershipCount}
</Badge>
</Link>
@@ -747,7 +747,7 @@ const EventTypeListHeading = ({
</div>
{!profile?.slug && !!teamId && (
<button onClick={() => publishTeamMutation.mutate({ teamId })}>
<Badge variant="gray" className="mb-1 -ml-2">
<Badge variant="gray" className="-ml-2 mb-1">
{t("upgrade")}
</Badge>
</button>
@@ -800,8 +800,7 @@ const CTA = () => {
const Actions = () => {
return (
<div className="hidden items-center md:flex">
<OrganizationEventTypeFilter />
<VerticalDivider />
<TeamsFilter popoverTriggerClassNames="mb-0" showVerticalDivider={true} />
</div>
);
};
@@ -834,9 +833,6 @@ const SetupProfileBanner = ({ closeAction }: { closeAction: () => void }) => {
);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any);
const EventTypesPage = () => {
const { t } = useLocale();
const router = useRouter();
@@ -862,6 +858,10 @@ const EventTypesPage = () => {
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filters = getTeamsFiltersFromQuery(router.query);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any, filters && { filters });
return (
<div>
@@ -879,47 +879,55 @@ const EventTypesPage = () => {
CTA={<CTA />}>
<WithQuery
customLoader={<SkeletonLoader />}
success={({ data }) => (
<>
{data.eventTypeGroups.length > 1 ? (
<>
{isMobile ? (
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
) : (
data.eventTypeGroups.map((group: EventTypeGroup, index: number) => (
<div className="flex flex-col" key={group.profile.slug}>
<EventTypeListHeading
profile={group.profile}
membershipCount={group.metadata.membershipCount}
teamId={group.teamId}
orgSlug={orgBranding?.slug}
/>
success={({ data }) => {
const isFilteredByOnlyOneItem =
(filters?.teamIds?.length === 1 || filters?.userIds?.length === 1) &&
data.eventTypeGroups.length === 1;
<EventTypeList
types={group.eventTypes}
group={group}
groupIndex={index}
readOnly={group.metadata.readOnly}
/>
</div>
))
)}
</>
) : data.eventTypeGroups.length === 1 ? (
<EventTypeList
types={data.eventTypeGroups[0].eventTypes}
group={data.eventTypeGroups[0]}
groupIndex={0}
readOnly={data.eventTypeGroups[0].metadata.readOnly}
/>
) : (
<CreateFirstEventTypeView />
)}
return (
<>
{data.eventTypeGroups.length > 1 || isFilteredByOnlyOneItem ? (
<>
{isMobile ? (
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
) : (
data.eventTypeGroups.map((group: EventTypeGroup, index: number) => (
<div className="flex flex-col" key={group.profile.slug}>
<EventTypeListHeading
profile={group.profile}
membershipCount={group.metadata.membershipCount}
teamId={group.teamId}
orgSlug={orgBranding?.slug}
/>
<EmbedDialog />
{router.query.dialog === "duplicate" && <DuplicateDialog />}
</>
)}
<EventTypeList
types={group.eventTypes}
group={group}
groupIndex={index}
readOnly={group.metadata.readOnly}
/>
</div>
))
)}
</>
) : (
data.eventTypeGroups.length === 1 && (
<EventTypeList
types={data.eventTypeGroups[0].eventTypes}
group={data.eventTypeGroups[0]}
groupIndex={0}
readOnly={data.eventTypeGroups[0].metadata.readOnly}
/>
)
)}
{data.eventTypeGroups.length === 0 && <CreateFirstEventTypeView />}
<EmbedDialog />
{router.query.dialog === "duplicate" && <DuplicateDialog />}
</>
);
}}
/>
</Shell>
</div>
+6 -3
View File
@@ -27,7 +27,7 @@ const Heading = () => {
<h3 className="font-cal max-w-28 sm:max-w-72 md:max-w-80 text-emphasis truncate text-xl font-semibold tracking-wide xl:max-w-full">
{t("insights")}
</h3>
<p className="text-default hidden text-sm md:block">{t("subtitle_analytics")}</p>
<p className="text-default mt-2 hidden text-sm md:block">{t("insights_subtitle")}</p>
</div>
);
};
@@ -35,6 +35,7 @@ const Heading = () => {
export default function InsightsPage() {
const { t } = useLocale();
const { data: user } = trpc.viewer.me.useQuery();
const features = [
{
icon: <Users className="h-5 w-5" />,
@@ -77,10 +78,12 @@ export default function InsightsPage() {
<></>
) : (
<FiltersProvider>
<div className="mb-8 mt-0 ml-auto flex w-full flex-wrap justify-between md:-mt-8">
<div className="ml-auto mt-0">
<Heading />
<Filters />
</div>
<Filters />
<div className="mb-4 space-y-6">
<BookingKPICards />
+1 -1
View File
@@ -19,7 +19,7 @@ export default function MaintenancePage() {
</Head>
<div className="bg-default m-auto rounded-md p-10 text-right ltr:text-left">
<h1 className="text-emphasis text-2xl font-medium">{t("under_maintenance")}</h1>
<p className="text-default mt-4 mb-6 max-w-2xl text-sm">
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
{t("under_maintenance_description", { appName: APP_NAME })}
</p>
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
+2 -1
View File
@@ -101,7 +101,8 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
const ssr = await ssrInit(context);
const user = await prisma.user.findFirst({
where: {
username,
/** TODO: We should standarize this */
username: username.toLowerCase().replace(/( |%20)/g, "+"),
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
+13 -2
View File
@@ -14,7 +14,7 @@ import PageWrapper from "@components/PageWrapper";
type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent }: PageProps) {
return (
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<BookerSeo
@@ -29,6 +29,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
rescheduleBooking={booking}
isAway={away}
hideBranding={isBrandingHidden}
isTeamEvent={isTeamEvent}
/>
</main>
);
@@ -57,6 +58,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
username: true,
},
},
team: {
select: {
id: true,
},
},
},
},
},
@@ -96,9 +102,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
}
const isTeamEvent = !!hashedLink.eventType?.team?.id;
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent });
if (!eventData) {
return {
@@ -114,6 +122,9 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
slug,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
// Sending the team event from the server, because this template file
// is reused for both team and user events.
isTeamEvent,
},
};
}
@@ -23,6 +23,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
eventSlug={slug}
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
isTeamEvent
/>
<Booker
username={user}
@@ -30,6 +31,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
rescheduleBooking={booking}
isAway={away}
hideBranding={isBrandingHidden}
isTeamEvent
/>
</main>
);
@@ -71,7 +73,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({ username: teamSlug, eventSlug: meetingSlug });
const eventData = await ssr.viewer.public.event.fetch({
username: teamSlug,
eventSlug: meetingSlug,
isTeamEvent: true,
});
if (!eventData) {
return {
@@ -0,0 +1 @@
export { getServerSideProps, default } from "../../../[user]/[type]";
@@ -0,0 +1,40 @@
import type { GetServerSidePropsContext } from "next";
import prisma from "@calcom/prisma";
import PageWrapper from "@components/PageWrapper";
import type { UserPageProps } from "../../../[user]";
import UserPage, { getServerSideProps as GSSUserPage } from "../../../[user]";
import type { TeamPageProps } from "../../../team/[slug]";
import TeamPage, { getServerSideProps as GSSTeamPage } from "../../../team/[slug]";
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const team = await prisma.team.findFirst({
where: {
slug: ctx.query.user as string,
parentId: {
not: null,
},
parent: {
slug: ctx.query.orgSlug as string,
},
},
select: {
id: true,
},
});
if (team) {
return GSSTeamPage({ ...ctx, query: { slug: ctx.query.user } });
}
return GSSUserPage({ ...ctx, query: { user: ctx.query.user } });
};
type Props = UserPageProps | TeamPageProps;
export default function Page(props: Props) {
if ((props as TeamPageProps)?.team) return TeamPage(props as TeamPageProps);
return UserPage(props as UserPageProps);
}
Page.PageWrapper = PageWrapper;
+1
View File
@@ -0,0 +1 @@
export { getServerSideProps, default } from "../../team/[slug]";
@@ -0,0 +1 @@
export { getServerSideProps, default } from "../../../../team/[slug]/[type]";
@@ -0,0 +1 @@
export { getServerSideProps, default } from "../../../../team/[slug]";
@@ -0,0 +1,9 @@
import UnverifiedOrgsPage from "@calcom/features/ee/organizations/pages/settings/admin/unverifiedOrgPage";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = UnverifiedOrgsPage as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
+1 -1
View File
@@ -25,7 +25,7 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
<h2 className="font-medium">{title}</h2>
<p>{description}</p>
</div>
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pt-0 sm:pl-3">{children}</div>
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pl-3 sm:pt-0">{children}</div>
</section>
<hr className="border-subtle" />
</>
@@ -57,7 +57,7 @@ const ApiKeysView = () => {
<div>
{isLoading ? null : data?.length ? (
<>
<div className="mt-6 mb-8 rounded-md border">
<div className="mb-8 mt-6 rounded-md border">
{data.map((apiKey, index) => (
<ApiKeyListItem
key={apiKey.id}

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