Compare commits

...
213 Commits
Author SHA1 Message Date
Alex van Andel 1929d5c7d4 v3.0.2
release.yml / release (push) Canceled after 0s
2023-06-19 13:33:13 +02:00
Papageorgiou Nikos 8ae54cd7cc fix: Storybook build and fix cal brand colors (#9588)
Storybook failed with satisfies operator
Fix cal brand colors in storybook
2023-06-19 13:28:49 +02:00
Hariom Balhara bb13009e64 + is %2B as well (#9624) 2023-06-19 11:22:13 +00:00
Jeroen Reumkens 648dd862f5 Tweaks to booker grid sizing to prevent buttons from overflow booker wrapper in Safari (#9620) 2023-06-19 11:34:49 +01:00
sean-brydonandzomars 8eccd3658e feat: Upstash implementation for rate limiting/redis (#9514)
* Introduce rate limiting that works on the edge

* typo

* Log once on init

* Update rateLimit.ts

---------

Co-authored-by: zomars <zomars@me.com>
2023-06-19 10:01:06 +00:00
Ujwal KumarandJeroen Reumkens ed65b2a3ab fix: Hide layout switcher when only 1 layout is enabled (#9610)
* Hide layout switcher when only 1 layouts is enabled

* Ran prettier

---------

Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
2023-06-19 08:55:25 +00:00
Manav Guptaandsean-brydon 0930121680 fix: event type filter in Mobile view (#9619)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-06-18 21:11:41 +00:00
Crowdin Bot 1068873f27 New Crowdin translations by Github Action 2023-06-18 20:55:42 +00:00
Udit TakkarandPeer Richelsen a1ece0be63 fix: sidebar ui (#9581)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-18 20:54:57 +00:00
Crowdin Bot c550b4c8fc New Crowdin translations by Github Action 2023-06-18 19:22:51 +00:00
VIvek ChavanandKeith Williams e95650637c fix: changes "cancelled" to canceled (#9505)
* fixed #9502 changes cancelled to cenceled

* fixed cancelled to cancelled from all places in common.json

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-18 19:21:52 +00:00
0f3e0a1554 fix: Alert component fixed (#9605)
* fix: Alert Component Fixed

* Update packages/ui/components/alert/Alert.tsx

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-17 20:57:15 +00:00
Peer Richelsen 761dfac033 3.0.1
release.yml / release (push) Canceled after 0s
2023-06-17 22:48:38 +02:00
Crowdin Bot 9fda4acc5b New Crowdin translations by Github Action 2023-06-17 20:45:08 +00:00
Jaideep Guntupalli 0470708aad refactor: translated request_password_reset in dutch (#9601) 2023-06-17 22:44:36 +02:00
Crowdin Bot bee392ba1a New Crowdin translations by Github Action 2023-06-17 20:43:01 +00:00
Hariom BalharaandPeer Richelsen 448f57442b Use the old booker redirect approach (#9589)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-17 20:42:15 +00:00
Crowdin Bot 856b090b4b New Crowdin translations by Github Action 2023-06-17 20:28:18 +00:00
Ujwal Kumar b99661a411 Fix typo in email verification banner (#9595) 2023-06-17 22:27:24 +02:00
Crowdin Bot d96a26eb3c New Crowdin translations by Github Action 2023-06-17 15:48:23 +00:00
Eric Dantas 9d08f5c7c8 chore: Create add-PRs-to-project-reviewing-PRs.yml (#9597)
* Create add-PRs-to-project-eric.yml

* Update and rename add-PRs-to-project-eric.yml to add-PRs-to-project-reviewing-PRs.yml

* Update add-PRs-to-project-reviewing-PRs.yml
2023-06-17 17:47:29 +02:00
Crowdin Bot 4fe5f6ca6f New Crowdin translations by Github Action 2023-06-17 01:06:55 +00:00
Esaú MoraisandAlex van Andel af505935bd fix: update top banner (#9580)
* fix: update wrong banner message in english

* fix: update `resend_email` translation in pt-BR

* chore: add `Icon` prop to `TopBanner` component

* fix: remove unnecessary variant scope

* Remove translations so they can be retranslated, complete design

* Changed key of already translated entry

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-06-17 01:06:04 +00:00
Leo Giovanetti 4a30f60622 Configuring totp code when verifying too (#9584) 2023-06-16 20:08:12 +00:00
Alex van Andel 24ed04e6c7 v3.0.0.2
release.yml / release (push) Canceled after 0s
2023-06-16 19:21:26 +02:00
Hariom Balhara bed3595314 fix: Dynamic Group Links on vercel (#9576)
* Potential fix

* Debug

* Debug

* Use the same logic in new-booker
2023-06-16 19:00:23 +02:00
Alex van Andel 74671263bf fix: New booker selected date issues (#9570)
* Pick correct selected date

* Commented out unused booking page revalidation

* Fixed selected default bookerLayout not being applied

* Use the right weekdayOfFirst - date(1) of browsingDate
2023-06-16 18:08:58 +02:00
Crowdin Bot bdc5e9c7e9 New Crowdin translations by Github Action 2023-06-16 14:39:35 +00:00
Leo Giovanetti 8fa1c70a97 Adding more time to org admin email verif step (#9574) 2023-06-16 14:38:40 +00:00
Hariom Balhara 1343c8fa8d fix: Booking Page crash with prefilling guests (#9566)
* Fix Booking Page crash with prefilling guests

* Update packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx
2023-06-16 15:02:59 +02:00
Crowdin Bot 5c0f9510a1 New Crowdin translations by Github Action 2023-06-16 12:46:06 +00:00
b2801170fa fix: Default tab selected issue (#9546)
* default tab selected issue

* refactor: improve cde

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

* fix: use onSuccess

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

* fix: move onsuccess and onerror

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: Vinoth Kumar V <vinoth_kumar_v@Vinoths-MacBook-Pro.local>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-16 12:45:20 +00:00
Alex van Andel 6ac19e8307 v3.0.0.1
release.yml / release (push) Canceled after 0s
2023-06-16 12:48:20 +02:00
Hariom Balhara 344241cfbf fix: New Booker Rewrites (#9557)
* Solve the problem using Next.config.js only

* Debug

* Avoid infinite rewritees

* Apply suggestions from code review

* Apply suggestions from code review

* Remove stale comments

* Update packages/embeds/embed-core/src/embed-iframe.ts
2023-06-16 12:31:57 +02:00
Carina WollendorferandCarinaWolli 66ebf18fa9 add team slug to team event link (#9558)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-16 11:05:47 +02:00
Crowdin Bot e6d6f3317e New Crowdin translations by Github Action 2023-06-16 08:38:03 +00:00
Carina WollendorferandCarinaWolli 55f66e6bcd remove slash (#9556)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-16 08:37:06 +00:00
Crowdin Bot 22e2b0a6a9 New Crowdin translations by Github Action 2023-06-16 06:27:50 +00:00
sydwardrae e24f3828cb Spelling chore common.json (#9550)
Fixed occured to occurred
2023-06-16 08:26:59 +02:00
Crowdin Bot 70f5bdc499 New Crowdin translations by Github Action 2023-06-15 22:38:02 +00:00
zomars 8731a39c3c fix: api build 2023-06-15 15:36:59 -07:00
Omar López e2d9e75475 v3.0.0
release.yml / release (push) Canceled after 0s
2023-06-15 15:15:39 -07:00
Crowdin Bot 55c7707b94 New Crowdin translations by Github Action 2023-06-15 22:15:20 +00:00
Crowdin Bot 98944d4815 New Crowdin translations by Github Action 2023-06-15 17:29:48 +00:00
Crowdin Bot faba25acbd New Crowdin translations by Github Action 2023-06-15 17:28:46 +00:00
aab5ee344c feat: Org settings - profile,appearance, child teams, create new child (#9231)
* 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

* 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

* A11ly

* Feedback

* 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

* 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

* 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

* 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

* Fix missing prop

---------

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: Hariom Balhara <hariombalhara@gmail.com>
2023-06-15 10:27:39 -07:00
Leo Giovanettiandzomars 6880f88dbe Org teams (#9544)
Co-authored-by: zomars <zomars@me.com>
2023-06-15 16:55:45 +00:00
Crowdin Bot 9177fe86dc New Crowdin translations by Github Action 2023-06-15 16:37:15 +00:00
bf3f8acbc0 feat: Enable new booker globally for 3.0 (#9541)
* Update migration to be on by default - remove cookie check

* Use env variable to control enabling NEW_BOOKER for all

* Update packages/prisma/migrations/20230524105015_added_newbooker_feature_flag/migration.sql

* Update turbo.json

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-06-15 16:36:25 +00:00
b4fa9826f2 feat: organizations sidebar (#9395)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
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: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-06-15 16:12:47 +05:30
Jeroen ReumkensandPeer Richelsen 955de3133c feat: weekly view calendar improvements (#9491)
* Add correct start/end time for weekly calendar by looking at calendar's availability

* Always show 24h in weekly calendar, added scroll functionality, current time and events in correct timezone

* Improved current time style

* Show slots for every minute, so also odd timeslots due to offsets work

* Show correct timeformat for weekly view on left side time stamps

* Fix data attr for debug

* Position events differently so we can accomodate for offset start times.

* Removed schedule from public event api, because we don't use it anymore

* Fixed alignment of timeslots in weekview calendar.

* Added loading spinner to weekly calendar

* Force weekly view calendar hour labels to show 00 minutes, also for gmt x.5 timezones that are offset 30 mins.

* Change event duration blocks in weekly calendar when user changes duration in multi duration event

* Improved week view slot time label alignments for shorter durations

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-15 09:59:58 +00:00
Jeroen Reumkens faac3d0f57 fix: Fixed merge mistake in next.config breaking new booker redirects. (#9532) 2023-06-15 14:30:44 +05:30
Crowdin Bot 3fb1ede770 New Crowdin translations by Github Action 2023-06-15 08:59:05 +00:00
Hariom Balhara 53224886e3 feat: Routing Forms/Teams Support (#9417) 2023-06-15 08:58:07 +00:00
Udit Takkar e513180d7e fix: check user session in banner (#9530)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-15 09:36:09 +01:00
Udit Takkar 3116d6df1a fix: Email verification banner (#9519) 2023-06-15 11:41:32 +05:30
4284a4d16d feat: Organizations users calendar cache (#9498)
* Initial commit

* Adding feature flag

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

* Change scopedMembers to orgMembers

* Change to orgUsers

* Letting duplicate slugs for teams to support orgs

* Covering null on unique clauses

* Supporting having the orgId in the session cookie

* feat: organization event type filter (#9253)

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

* Missing changes to support orgs schema changes

* 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

* Making sure we let localhost still work

* Feedback

* Type check fixes

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

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

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

* First step done

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

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Org branding provider used in shell sidebar

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Using org avatar (temp)

* Not showing org logo if not set

* User onboarding with org branding (slug)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Feedback

* Org public profile

* Public profiles for team event types

* Added setup profile alert

* Using org avatar on subteams avatar

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

* Profile username availability rely on org hook

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

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

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

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

---------

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

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

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

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

* First step done

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

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Org branding provider used in shell sidebar

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Using org avatar (temp)

* Not showing org logo if not set

* User onboarding with org branding (slug)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Feedback

* Org public profile

* Public profiles for team event types

* Added setup profile alert

* Using org avatar on subteams avatar

* Processing orgs and children as profile options

* Reverting change not belonging to this PR

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

* Removing console.log

* Comparing memberships to choose the highest one

---------

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

* Type errors

* Refactor and type fixes

* Update orgDomains.ts

* Feedback

* Reverting

* NIT

* fix issue getting org slug from domain

* Improving orgDomains util

* Host comes with port

* Update useRouterQuery.ts

* add org users cache-calendar

* fix typo

* re-use the simple user calendar-cache page

* Apply suggestions from code review

* Update packages/core/CalendarManager.ts

* type fixes

---------

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: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2023-06-15 00:40:47 +00:00
zomars eadca3b2c0 Update .env.example 2023-06-14 14:45:53 -07:00
Crowdin Bot 68125af29f New Crowdin translations by Github Action 2023-06-14 21:44:55 +00:00
Crowdin Bot d436e628fe New Crowdin translations by Github Action 2023-06-14 21:41:30 +00:00
c5483c81de feat: Organizations (#8993)
* Initial commit

* Adding feature flag

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

* Change scopedMembers to orgMembers

* Change to orgUsers

* Letting duplicate slugs for teams to support orgs

* Covering null on unique clauses

* Supporting having the orgId in the session cookie

* feat: organization event type filter (#9253)

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

* Missing changes to support orgs schema changes

* 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

* Making sure we let localhost still work

* Feedback

* Type check fixes

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

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

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

* First step done

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

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Org branding provider used in shell sidebar

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Using org avatar (temp)

* Not showing org logo if not set

* User onboarding with org branding (slug)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Feedback

* Org public profile

* Public profiles for team event types

* Added setup profile alert

* Using org avatar on subteams avatar

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

* Profile username availability rely on org hook

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

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

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

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

---------

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

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

* Desktop first banner, mobile pending

* Removing dead code and img

* WIP

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

* First step done

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

* Step 2 done, avatar not working

* Covering null on unique clauses

* Onboarding admins step

* Last step to create teams

* Moving change password handler, improving verifying code flow

* Clearing error before submitting

* Reverting email testing api changes

* Reverting having the banner for now

* Consistent exported components

* Remove unneeded files from banner

* Removing uneeded code

* Fixing avatar selector

* Org branding provider used in shell sidebar

* Using meta component for head/descr

* Missing i18n strings

* Feedback

* Making an org avatar (temp)

* Using org avatar (temp)

* Not showing org logo if not set

* User onboarding with org branding (slug)

* Check for subteams slug clashes with usernames

* Fixing create teams onsuccess

* feedback

* Feedback

* Org public profile

* Public profiles for team event types

* Added setup profile alert

* Using org avatar on subteams avatar

* Processing orgs and children as profile options

* Reverting change not belonging to this PR

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

* Removing console.log

* Comparing memberships to choose the highest one

---------

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

* Type errors

* Refactor and type fixes

* Update orgDomains.ts

* Feedback

* Reverting

* NIT

* fix issue getting org slug from domain

* Improving orgDomains util

* Host comes with port

* Update useRouterQuery.ts

* Feedback

* Feedback

* Feedback

* Feedback: SSR for user event-types to have org context

* chore: Cache node_modules (#9492)

* Adding check for cache hit

* Adding a separate install step first

* Put the restore cache steps back

* Revert the uses type for restoring cache

* Added step to restore nm cache

* Removed the cache-hit check

* Comments and naming

* Removed extra install command

* Updated the name of the linting step to be more clear

* Removes the need for useEffect here

* Feedback

* Feedback

* Cookie domain needs a dot

* Type fix

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

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

* Update packages/emails/src/templates/OrganizationAccountVerifyEmail.tsx

* Feedback

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
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: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-14 21:40:20 +00:00
Keith Williams 9db4a04cd9 chore: Cache node_modules (#9492)
* Adding check for cache hit

* Adding a separate install step first

* Put the restore cache steps back

* Revert the uses type for restoring cache

* Added step to restore nm cache

* Removed the cache-hit check

* Comments and naming

* Removed extra install command

* Updated the name of the linting step to be more clear
2023-06-14 15:27:47 -03:00
Alex van Andel 8943e8c2f6 Fix: Sets null to undefined during workflow select (#9513) 2023-06-14 14:43:42 +00:00
Crowdin Bot f17fa55426 New Crowdin translations by Github Action 2023-06-14 14:39:25 +00:00
Hariom BalharaandLeo Giovanetti 9ff83075d6 Improve IP Ban (#9512)
Co-authored-by: Leo Giovanetti <hello@leog.me>
2023-06-14 16:38:34 +02:00
Jeroen Reumkens 6b6043afdc Added custom error with more details to debug the ciaran event issue (#9482) 2023-06-14 10:47:58 +00:00
Crowdin Bot fd9162e70f New Crowdin translations by Github Action 2023-06-14 09:54:17 +00:00
Jeroen ReumkensandHariom Balhara 8331059b7c feat: new booker embed (#9023)
* Added embed files for new booker routes.

* Updated nextjs config to redirect to correct embed url.

* Notify embed of booker size after animation completes.

* Don't use animations and 100vh in embed.

* Added booker layout toggles to embed settings (wip)

* Booker header positioning tweaks

* Show correct layout in embed

* Don't show pricing event meta if stripe app is not installed

* Support custom border booker for embed

* wip on passing layout prop to iframe for previews

* Only show booker layout settings in embed-create-popup if booker layout feature is enabled.

* Made layout prop in preview state for embed optional

* Made layout prop in preview state for embed optional

* Get layout toggle working

* Fixed types

* Added themebasis prop

* Fix potential circular import that should then fix type errors

* Fixed type error by fixing casing.

* Type fixes

* Added translations

* More translations

* Remove comment

* Removed uneccessary addition to package.json

* Removed todo since it's solved in another way

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-06-14 09:22:44 +00:00
Syed Ali Shahbaz f0992b4914 fix with object undefined check (#9493) 2023-06-13 20:27:42 +00:00
TachyonicBytesandTachyonicBytes e081a4cfc2 fix: Add e2e test for routing forms webhook subscription (#9401)
* add e2e test for routing forms webhook subscription

* remove debugging checks

* fixed missing method for e2e

* remove forced timeout for e2e test

* fix e2e webhook test

* use routing forms page for install

---------

Co-authored-by: TachyonicBytes <support@tachyonicbytes.com>
2023-06-13 15:22:19 +00:00
895b3ca7c9 feat: add paid webhook (#8721)
* feat: add BOOKING_PAID on enums

* feat: trigger the BOOKING_PAID webhook

* refactor: create payment variable

* feat: add REJECTED and REQUESTED on enums

* feat: create migration to booking werbhooks

* Update packages/features/webhooks/lib/constants.ts

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>

* Update packages/prisma/schema.prisma

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>

* Update packages/prisma/migrations/20230513235419_add_booking_webhooks/migration.sql

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>

---------

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-06-13 14:57:59 +00:00
Jaideep GuntupalliandBailey Pumfleet 4d30264950 fix: Multiple In Person Locations not working as expected in Event Setup Tab (#9465)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-06-13 14:54:53 +00:00
3a352a94f6 fix: phone input update state persistence (#9471)
* fix: phone input update state persistence

* refactor: use spreading

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: alannnc <alannnc@gmail.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-13 14:54:13 +00:00
Nikeshkumar TK 8623175612 fix: Routing form two "New" button issue. (#9479)
* event types public api fix

* Routing form new button issue

* Routing form new button issue

* Routing form new button issue

* Routing form new button issue

* Routing form new button issue
2023-06-13 14:53:50 +00:00
Udit TakkarandPeer Richelsen ea531777c4 fix: size of cell (#9486)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-13 11:08:00 +00:00
Soham Datta 2639ef7e13 fix: #9217 | redirected unready /admin to one of its tabs (#9222) 2023-06-13 10:46:41 +00:00
Jeroen Reumkens 0e76a19c96 Pass event duration to schedule api call (#9484) 2023-06-13 10:14:45 +00:00
3ec9eb377d docs: Added command in manual section (#9433)
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-06-12 21:46:29 +01:00
Udit TakkarandBailey Pumfleet 44b7000509 fix: dark mode in Alert component (#9467)
* fix: dark mode in Alert component

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>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-06-12 17:49:44 +00:00
depfu[bot]andHariom Balhara 251307f1e6 chore: Update vite 2.9.15 → 2.9.16 (patch) (#9346)
* Update vite to version 2.9.16

* Upgrade vite to 4.1.2 everywhere

---------

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-06-12 16:51:02 +00:00
Balveer Singh RaoandUdit Takkar 75e638ec4d fix: copy icon for dark mode (#9459)
* fixed copy icon for dark mode

* fix: height

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-12 16:48:37 +00:00
Crowdin Bot 443ccdd6c0 New Crowdin translations by Github Action 2023-06-12 16:28:32 +00:00
Abhishek Sunil dbf11cc04b test: Add E2E test to verify theme change (#9455)
* Add E2E test to verify theme change

* PR fixes
2023-06-12 16:14:40 +00:00
Crowdin Bot a5c9729f5f New Crowdin translations by Github Action 2023-06-12 10:57:30 +00:00
sean-brydon a6e2983a22 Fix translations (#9466) 2023-06-12 10:43:50 +00:00
Meet Patel a21f0c46c7 enabled csv upload for team bulk invites (#9446) 2023-06-11 09:36:59 +00:00
Udit Takkar ed4a81e852 fix: theme in new booker (#9453)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-11 09:11:46 +00:00
9231218350 fix: carify what sender ID means (#9431)
* add tooltip on info icon

* fix text

* fix UI in addActionDialog

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-06-11 03:20:13 +00:00
7d1ea4ee2b chore: moved viewport meta from document to PageWrapper (#9406)
* moved viewport meta from document to _app

* moved viewport meta from _app.tsx to PageWrapper.tsx

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
2023-06-10 15:19:17 +00:00
Efraín Rochín 0c68e51930 fix: [CAL-1765] opt-in: clicking "confirm" or "reject" broken (#9443)
* Add support to a custom sessionGetter in trpc createContext

* type fixes
2023-06-10 11:02:50 +00:00
Alex van Andel 8e565dc610 v2.9.8
release.yml / release (push) Canceled after 0s
2023-06-10 12:46:24 +02:00
Rob JacksonandUdit Takkar 5b47844fa1 fix: Able to toggle off timeFormat on new booker UI (#9442)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-06-10 07:51:49 +00:00
Rob JacksonandEfraín Rochín 0d01d3246a test: Add E2E tests for seated attendee rescheduling or cancelling bookings (#9422)
* Add test for attendees rescheduling bookings

* Add tests for rescheduling seated bookings (first attendee)

* Fix tests not skipping disabled times on new booker UI

* Add tests for rescheduling seated bookings (all attendees)

* Remove duplicate tests (my mistake)

* Add explicit test ids for cancel inputs

* Add E2E tests for seated attendees cancelling their bookings

* Update E2E tests for a seated attendee rescheduling their booking

* Update tests to remove dependence on localised text

* Updated seated E2E tests to remove testing of unused URL

---------

Co-authored-by: Efraín Rochín <roae.85@gmail.com>
2023-06-10 03:47:50 +00:00
Alex van AndelandEfraín Rochín bb31c4ba14 fix: Prevent invalid host assignment (#9441)
* Avoids invalid hosts

* Fixed test selector, instead using react-select-2-listbox

* Different way of selecting the managed user

* Convert select-option- to value instead of label

* Replaced all instances of networkidle

* done rewriting test

* Fix prisma is not defined error

* Depend on inner text rather than testid

* Fill of team create is unstable

* Removed console.log

---------

Co-authored-by: Efraín Rochín <roae.85@gmail.com>
2023-06-10 02:39:49 +00:00
Crowdin Bot d9a7952467 New Crowdin translations by Github Action 2023-06-09 22:33:12 +00:00
Balveer Singh Rao 208dcbbda3 fixed Rotate spinner (#9429) 2023-06-09 22:21:54 +00:00
Crowdin Bot ff4494d395 New Crowdin translations by Github Action 2023-06-09 20:45:51 +00:00
Udit Takkar db4b32119e fix: UI bugs (#9435)
* fix: toggle group background

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

* fix: background

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

* chore

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

* fix: confirm modal

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

* fix: dark mode footer

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

* fix: change tootltip

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-09 20:32:35 +00:00
Carina WollendorferandCarinaWolli bfef5b1877 fix: team bio (#9438)
* fix bio rendering

* fix type error

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-09 12:50:35 +00:00
Carina WollendorferandCarinaWolli 91962cc90b check that input teamId equals webhook team (#9434)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-09 11:18:59 +00:00
Carina WollendorferandCarinaWolli 7a71a5ddd4 fix error when updating event type availability (#9428)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-06-09 10:19:49 +00:00
Jeroen Reumkens ca47a589ba feat: weekly view for new booker. (#9389)
* WIP on weekly view for new booker.

* Set responsive grid height for booker weekly view.

* Fixed types

* Ensure you can't click on disabled dates in weekly calendar

* CAL-1916: Improvements for blocked out parts background blocks, made the lines connect by using a shared background.

* Margin, padding and color tweaks for new booker CAL-1911

* Made week view have more days visible by default

* Fix to prevent layout switch from breaking when clicking same layout twice.
2023-06-09 09:38:18 +00:00
Crowdin Bot a33dbd8922 New Crowdin translations by Github Action 2023-06-09 08:31:50 +00:00
aaab4ca741 refactor: TwoFactor.tsx (#9392)
* Update TwoFactor.tsx

* fix: useeffect

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 <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-09 08:19:33 +00:00
Crowdin Bot 6aadf7429d New Crowdin translations by Github Action 2023-06-08 16:57:52 +00:00
GitStart-Cal.comandgitstart-calcom 748307bc91 fix: Wrong padding on modals - second version (CALCOM-8414) (#9419)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-06-08 16:45:06 +00:00
Crowdin Bot 559389a319 New Crowdin translations by Github Action 2023-06-08 14:37:40 +00:00
Ninad Trivedi a9140b6a76 Scrollbar bug fixed (#9357) 2023-06-08 14:25:23 +00:00
Crowdin Bot 8f50e873c9 New Crowdin translations by Github Action 2023-06-08 13:53:38 +00:00
9b28b7f78a fix: remove duplicate value i18n (#8547)
* fix: remove duplicate value i18n

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

* feat: add duplicate check in next config

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

* fix: remove more duplicates

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

* fix: description on two factor page

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

* Update apps/web/next.config.js

* Update apps/web/next.config.js

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-08 13:37:54 +00:00
Crowdin Bot 4417097f97 New Crowdin translations by Github Action 2023-06-08 12:45:11 +00:00
Jeroen Reumkens 93fc036d2c feat: Made booker layout settings more user friendly (#9397)
* Made booker layout settings more user friendly

* Fixed tyeps and default values in appearance form for booker layout

* Enable all layouts by default
2023-06-08 12:32:17 +00:00
Omar López 7f13fc58b9 v2.9.7
release.yml / release (push) Canceled after 0s
2023-06-07 16:02:32 -07:00
alannncandAlex van Andel 527d1f6c93 fix: remove bg-color (#9408)
* remove bg-color

* Update packages/app-store/_components/AppCard.tsx

Fix lint error trailing ,

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-06-07 16:02:32 -07:00
Omar López 0ec3c496d8 fix: missing company name in emails (#9409) 2023-06-07 16:02:32 -07:00
Jeroen Reumkens 62ec3de0b1 Recurring event doesn't neccessarily mean an event that requires confirmation. We need to show the recurring option nevertheless. (#9393) 2023-06-07 16:02:32 -07:00
Jeroen Reumkens 2c47cb6a3b Only take owner data we need in public event api, instead of everything. Saves bandwidth. (#9394) 2023-06-07 16:02:32 -07:00
Jeroen Reumkens dc2b145843 Show header layout in large views even if theres only one layout enable. Don't show toggle though. (#9398) 2023-06-07 16:02:32 -07:00
Nafees Nazik c958e1578a feat: use the correct locale for the title (#9286) 2023-06-07 16:02:32 -07:00
Crowdin Bot 1e857e5c0a New Crowdin translations by Github Action 2023-06-07 20:08:38 +00:00
Peer Richelsen 9bc8d48415 fix: leading none in shell (#9404) 2023-06-07 19:57:10 +00:00
f86c6734a7 fix: Wrong padding on "Invite Team Members" modal (CALCOM-8414-FIX) (#9273)
* Fix margin from invitation modal content

* Change margin to padding in Dialog component, so the divider absolute property works properly

* Update Dialog.tsx padding from the button top from 24px to 16px

* Change Dialog content padding to match figma design, having the padding bottom 16px

* Update FormBuilder.tsx to match figma

* Update FormBuilder.tsx Dialog content inner div paddings

* Update FormBuilder.tsx, remove relative to fix divider

* Fixing padding across dialogs

* nit

* Fixes e2e

* undo wrong padding

* fixed footer with divider

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: zomars <zomars@me.com>
2023-06-07 19:55:47 +00:00
HitenandPeer Richelsen 3d5ff82062 Fix width issue in general setting (#9388)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-07 11:08:29 +00:00
Crowdin Bot 8b63483e2e New Crowdin translations by Github Action 2023-06-07 10:56:44 +00:00
TheOnlyWayUpandPeer Richelsen c97d4288c0 typo(README): 'concent' -> 'consent' (#9356)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-07 11:55:45 +01:00
Hariom Balhara ff75364f22 Fix embed detection (#9385) 2023-06-07 08:43:12 +00:00
Crowdin Bot 7f1b028099 New Crowdin translations by Github Action 2023-06-07 07:40:33 +00:00
9e70a985e4 feat: email verification (#9081)
* Verify - inital email commit

* Add token type - api redirect - migration

* Redirect and valid api callback route

* Update email design

* Change signup URL to redirect to verify-email

* Add feature flag - add a11y text to email - add top banner

* Shell shouldnt redirect to onboarding if the user needs to verify account

* Move flag check to server

* Cleanup

* Rate limit

* Fix redirects

* Remove api signup mess

* Double negation for forced bool

* Fix props

* Update packages/emails/templates/account-verify-email.ts

* Enable migration by default

* Fix typos

* Fix google verify issue

* Update packages/features/auth/lib/verifyEmail.ts

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

* NITS: @harioms addressed

* Remove schema changes

* Fix NITs+ improvments

* Update apps/web/pages/api/auth/verify-email.ts

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

* Update packages/features/ee/common/components/LicenseRequired.tsx

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

* Update apps/web/pages/api/auth/verify-email.ts

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

* Always preloads feature flags

* Update verifyEmail.ts

* Update schema.prisma

* Type fix

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-06-07 07:27:48 +00:00
Crowdin Bot 057cf273dd New Crowdin translations by Github Action 2023-06-07 02:15:50 +00:00
Omar López 9af621294d fix: Prevents undefined features flags on first render (#9374) 2023-06-07 02:03:52 +00:00
zomars bfb5a31032 Fix broken test introduced in #9085 2023-06-06 17:35:03 -07:00
Hariom BalharaandOmar López 6308955e89 fix: embed non-transparent dark background on success page (#9085)
* Fix colorscheme dark on success page due to embed not detected properly there

* Use calNamespace in getEmbediframe

* Fix missing variable

* Update basic.e2e.ts

---------

Co-authored-by: Omar López <zomars@me.com>
2023-06-06 23:52:13 +00:00
Crowdin Bot b0c5b629d7 New Crowdin translations by Github Action 2023-06-06 23:52:45 +00:00
10f965570b feat: Add copy invite link (#8355)
* feat: Add shared invite link

* refactor: Rename Invite to Team Invite model

* feat: add admin check for team invite link procedures

* Replace TeamInvite with VerificationToken

* Add team invite null checks

* Migrates tRPC procedures to new format

* Type fixes

* Update common.json

---------

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-06 23:34:14 +00:00
Udit TakkarandPeer Richelsen c24f5200f5 fix: opt in booking from email (#9358)
* fix: opt in booking from email

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

* fix: undo this

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: type error

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

* fix: Avada Kedavra type errors

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-06 21:04:06 +00:00
zomars f922692a1a Sync yarn.lock with website 2023-06-06 12:07:37 -07:00
Crowdin Bot 65c137f67d New Crowdin translations by Github Action 2023-06-06 18:44:00 +00:00
Efraín Rochínandzomars 585679969a perf: Avoid useless dependencies in calendar cache page (#9290)
* increases the time range to cover all time zones

* it isolates the getCachedResults and rename to getCalendarsEvents

* remove unrelated changes

* remove unrelated changes

---------

Co-authored-by: zomars <zomars@me.com>
2023-06-06 18:30:29 +00:00
Crowdin Bot 893b67b6c4 New Crowdin translations by Github Action 2023-06-06 18:16:49 +00:00
Crowdin Bot 3c296b440e New Crowdin translations by Github Action 2023-06-06 18:15:56 +00:00
Crowdin Bot f8a59b52f7 New Crowdin translations by Github Action 2023-06-06 18:14:59 +00:00
Efraín RochínandKeith Williams 17d106cfda chore: Replace UI login with api login on e2e tests (#9344)
* Replace the ui login with api login

* test api login

* go to first page after login in some tests

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-06 18:02:32 +00:00
Crowdin Bot dcf9d30382 New Crowdin translations by Github Action 2023-06-06 17:48:34 +00:00
eafd7f6b77 fix: Disabled pinch to zoom on mobile devices#9155 (#9163)
* Disabled pinch to zoom on mobile devices#9155

* Modified the better fix to the feature

* Update apps/web/pages/_document.tsx

* Update apps/web/pages/_document.tsx

* Update apps/web/styles/globals.css

* Update apps/web/pages/_document.tsx

* Update apps/web/pages/_document.tsx

* Update apps/web/pages/_document.tsx

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-06 17:35:19 +00:00
f31165b442 feat: event settings booker layout toggle (#9082)
* WIP for adding booker layout toggle in event settings pages

* Prevent form error from getting form stuck in loading state

* Fixed types for bookerlayouts settings and preselect correct layout in booker

* Added defaultlayout settings to profile too, and use that in booker plus as default for events.

* Made layout settings responsive

* Added feature toggle for new layout settings

* Fixed user builder for tests by adding defaultlyotu

* Show toggles on booker for layout switch based on selected layouts. Also added a small fix for the settings toggles to preselect the correct toggle for defaultlayout when user profile settings are used.

* Used zod parse to fix type errors.

* Fix unit test

* Set selected date to today in datepicker when week or column view is default layout. It uses that date to show in the title bar.

* Moved booker layout settings to event and user meta data instead of new db column.

* Converted booker layout strings into an enum.

* Renamed booker layouts feature flag and deleted unused v2 booker feature flag.

* Update packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

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

* Fix import

* Fix lint warnings in EventTypeSingleLayout

* Fixed bug where when selected date was passed via query param page booking form wouldn't automatically show up. It would still serve you the date selection. This should fix e2e tests.

* Fixed layout header.

* Enabled booking layout toggle feature flag.

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-06-06 15:31:43 +00:00
Crowdin Bot 40c5d5871e New Crowdin translations by Github Action 2023-06-06 14:09:06 +00:00
Jeroen Reumkensandsean-brydon 2362754356 feat: CAL-1674 Added new header layout for new booker. (#9227)
* CAL-1674: Added new header layout for new booker.

* Removed unneccesary bg style for toggle group, since it's already the default bg color.

* Fixed types

* Gave minwidth to booker header date title, to prevent arrows for switching weeks from jumping around.

* Push small calendar down

* Fixed overflow in small calendar view

* Added booker modal padding underneath badges

* Fixed grid area name causing broken animation. Also use setstyle directly instead of using animate for non animated properties. Performs better and removes some errors by framer motion for non animatable units like 'auto'.

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-06-06 13:55:27 +00:00
Crowdin Bot 8ccf3b589e New Crowdin translations by Github Action 2023-06-06 12:14:47 +00:00
2dbc73c75b chore: fix all Typescript warnings (#8618)
* [CAL-1517] fix all Typescript warnings

* solve conflicts

* Update stripeCheckoutSession.handler.ts

Parse is a guard, so even though the variable is unused the parse itself is needed.

* Update ToolbarPlugin.tsx

Don't change dependency tree

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-06-06 11:59:57 +00:00
Ben Hybert 87e1ad7115 chore: switch over to use variable for app/companyName (#9334)
* switch over to use variable for app/companyName
* enable_apps_description - appName
* recording_from_your_recent_call - appName
* the_calcom_team - companyName
* calcom_is_better_with_team - appName

* capitalise 'S' in teams page.
2023-06-06 11:58:05 +00:00
il3ven 2e9895aceb add telegramvideo to packages/app-tore/index.ts (#9324)
`telegramvideo` seems to be missing and causing issue #9308
2023-06-06 11:24:54 +00:00
fbbc573f33 chore: remove deprecated metamask and rainbow (#9299)
* Remove deprecated code related to rainbow

* Remove translations for rainbow specific features

* Remove english translations for metamask

* Remove translations for metamask

* Fix lint issues

* Fix prettier issues

* update yarn lock

* Generated yarn.lock with submodules

* Added auth submodule deps

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-06-06 11:18:44 +00:00
Piyush GargandPeer Richelsen 8bc8a045b6 feat: email undefined value (#9240)
* Fix: #9210

* Fix: #9215 Added Password Validation to App Signup

* FIX #9216 Fixed the Update Button Allignment

* Fix #9238: Don't render descripiton in email template if undefined

* Remove Prev Changes

* Remove unwanted space

* Revert yarn.lock

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-06 10:03:40 +00:00
Crowdin Bot 8c049b29b3 New Crowdin translations by Github Action 2023-06-06 09:47:49 +00:00
ravi kunwarandUdit Takkar f1e783f047 fix: Availability Name does Not Update (#9322)
* fix: Availability Name does Not Update

* fix: use i18n in navigation

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-06 09:33:34 +00:00
Crowdin Bot 3baa6c7145 New Crowdin translations by Github Action 2023-06-06 07:17:36 +00:00
dd28bb46db fix: title (#9055)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-06 07:04:54 +00:00
c182091fa9 fix: /event-type /apps - doesn't match designs (#9317)
* updated AppCard comp for design changes

* updated rainfallForm for design changes

* Fix:: event-type/apps design issues

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

* Update packages/app-store/_components/AppCard.tsx

* Update packages/app-store/_components/AppCard.tsx

* Fix lint

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-05 22:49:27 +00:00
Crowdin Bot d061572ed6 New Crowdin translations by Github Action 2023-06-05 22:34:31 +00:00
Ujwal Kumarandalannnc 3a943fbc81 Fix background colour for show on booking page switch on event types page (#9318)
Co-authored-by: alannnc <alannnc@gmail.com>
2023-06-05 22:24:10 +00:00
Crowdin Bot 8a58a51a2c New Crowdin translations by Github Action 2023-06-05 12:20:39 +00:00
Crowdin Bot e92265ceaa New Crowdin translations by Github Action 2023-06-05 12:19:41 +00:00
Udit Takkar b7b1fff1c0 fix: hide 2fa tab when using google as identity provider (#9333)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-06-05 12:04:48 +00:00
Crowdin Bot 886f7021f4 New Crowdin translations by Github Action 2023-06-05 10:13:18 +00:00
Alex van Andel 2b17188087 Fixup tsconfig.json in packaged embed-react (#9330) 2023-06-05 09:57:53 +00:00
Ayush Mainali d498988bd4 fix: fixes page rendering comments initially (#9270) 2023-06-05 09:55:10 +00:00
Hariom Balhara 785a08ba2c Fix duplicate query param missing issue (#9328) 2023-06-05 09:50:34 +00:00
Hariom BalharaandBailey Pumfleet 6aa83102e4 Add stricter self-review guideline (#9327)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-06-05 11:00:04 +01:00
Ujwal Kumar 97fe41f3ba Fix hardcoded label of identifier label in form builder (#9249) 2023-06-05 09:47:08 +00:00
Piyush GargandPeer Richelsen b7cecfbb66 fix: Skeleton Padding and Width Issue (#9310)
* Fixed the Skeleton Padding and Width Issue

* Fix the Icon Bounce on Skeleton

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-05 09:24:16 +00:00
Anwar Sadath 7f7d80e653 fix: /getting-started/connected-calendar Toggle the first calendar on (#9028)
* [CAL-115] /getting-started/connected-calendar Toggle the first calendar on

* Fix lint error

* Fix error shown after completion of onboarding by connecting to calendars and playing around with toggle

* Fix type errors

* Fix first calendar selected even in all the screens wherever connectedCalendars is used
2023-06-05 09:21:50 +00:00
Ujwal Kumarandsean-brydon 64796a96fc Fix alignment of update and cancel button on sign up flow (#9218)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-06-05 09:16:28 +00:00
Crowdin Bot 83cc7dc86e New Crowdin translations by Github Action 2023-06-05 09:23:16 +00:00
Nafees Nazik 75c9145558 feat: add disable signup feature flag (#9274)
* feat: add feature flag

* feat: use the feature flag
2023-06-05 09:08:16 +00:00
Nafees Nazik 869f459d98 fix: title in booking submitted page (#9326) 2023-06-05 07:28:03 +00:00
Crowdin Bot e00008b663 New Crowdin translations by Github Action 2023-06-05 06:04:16 +00:00
175f2482e1 fix: Update forms UI (#8093)
* Routing Forms: Update forms UI

* fix failing test

* fix: Routing Forms forms UI Update

* fix: Routing Forms forms UI Update

* Apply suggestions from code review

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-06-05 05:51:19 +00:00
Crowdin Bot 9d91d72a72 New Crowdin translations by Github Action 2023-06-04 18:58:28 +00:00
Ujwal Kumar fd9cd1b98b fix: background colour for disabled select option (#9320)
* Fix disabled bg colour in CheckedTeamSelect

* Fix disabled bg colour in CheckedSelect, CheckedUserSelect, ChildrenEventTypeSelect
2023-06-04 18:46:19 +00:00
Crowdin Bot 2640040359 New Crowdin translations by Github Action 2023-06-03 11:13:34 +00:00
alannnc dec8f8e896 add team validations (#9300) 2023-06-03 11:02:23 +00:00
Efraín RochínandOmar López b4eff25636 fix: Issues with calendar sync at the beginning and end of month (#9282)
* increases the time range to cover all time zones

* remove console.log

---------

Co-authored-by: Omar López <zomars@me.com>
2023-06-02 20:17:53 +00:00
Alex van Andel 2a752e5358 v2.9.6
release.yml / release (push) Canceled after 0s
2023-06-02 22:18:28 +02:00
Syed Ali Shahbaz b2684837c1 Remove end as mandatory property (#9297) 2023-06-02 20:01:30 +00:00
Crowdin Bot 2fed2133a5 New Crowdin translations by Github Action 2023-06-02 19:44:04 +00:00
Alex van Andel 7fc0877c26 Set reqBody.end to start + eventType.length (#9296) 2023-06-02 19:29:52 +00:00
Crowdin Bot f7ce774ddc New Crowdin translations by Github Action 2023-06-02 18:42:40 +00:00
174c730f1f chore: add canonical to the document via PageWrapper (#8639)
* add canonical to the document via `PageWrapper`

* use WEBSITE_URL constant to create the canonical url value

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Efraín Rochín <roae.85@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-02 18:28:03 +00:00
Syed Ali Shahbaz 8ad513e4ec fix typo in swagger (#9278) 2023-06-01 21:09:18 +00:00
a0bf5b4067 test: Smoke Tests for packaged embeds and build improvements (#9169)
* Fix 2 Factor Auth

* Add a sandbox to verify types of embed-react

* Add fault types location

* Fix type location again

* Break types

* Ensure that builds are done again when doing pbublish

* Debug failure in CI

* Make sure unit test files arent used by playwright

* Fix embed-react test description

* Update .github/workflows/e2e-embed-react.yml

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

* Remove unnecessary log

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
2023-06-01 20:41:30 +00:00
Hariom BalharaandOmar López 484f603c9e fix: Routing Forms, Number operators were using string operands (#9182)
* Fix operators for number

* Add number operator unit tests

* Fixes and remove between operators

---------

Co-authored-by: Omar López <zomars@me.com>
2023-06-01 20:29:13 +00:00
zomars 94b16fb327 Update yarn.lock 2023-06-01 12:29:49 -07:00
Jeroen Reumkens 55db7bdf76 Make new booker work with private links (#9260) 2023-06-01 18:55:43 +00:00
Crowdin Bot 14af241d5f New Crowdin translations by Github Action 2023-06-01 18:57:05 +00:00
Keith Williams 4efa90e42f chore: Upgrade turbo (#9271)
* chore: Upgrade turbo

* Pinned turbo version

* Revert "Pinned turbo version"

This reverts commit ae263009ee4750943e25efe62052152b40655844.

* Upgraded to 1.10.1
2023-06-01 18:43:21 +00:00
Peer Richelsen 581e1759c4 chore: prevent merge conflicts in common.json (#9268) 2023-06-01 15:47:43 +00:00
Crowdin Bot 61cdac321e New Crowdin translations by Github Action 2023-06-01 15:09:08 +00:00
Ayush MainaliandPeer Richelsen 59a04137a1 fix: fixed width resizing of colorpicker (#9233)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-01 14:53:39 +00:00
Crowdin Bot eba1a1ffff New Crowdin translations by Github Action 2023-06-01 12:30:28 +00:00
Nafees Nazik df25b179d7 fix: use skeleton loader (#9264) 2023-06-01 12:17:11 +00:00
Ted Spare ff2d9ad67d Use github-script to safely label PRs from forks (#9256) 2023-06-01 11:48:36 +00:00
Crowdin Bot 485b72cd0d New Crowdin translations by Github Action 2023-06-01 11:56:48 +00:00
Jeroen ReumkensandPeer Richelsen 57eb5a4328 Fix for booker colors in dark mode, we can't use bg-default in both places because we use a custom background in dark mode. Otherwise it would be a similar color in dark. So we need to supply a custom dark mode color. (#9259)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-01 11:38:52 +00:00
Peer Richelsen 2c4954e31c Update package.json
release.yml / release (push) Canceled after 0s
2023-06-01 12:43:56 +01:00
Alex van Andel 748f2b42e8 Remove debugger; calls (#9262) 2023-06-01 11:28:59 +00:00
Nafees NazikandKeith Williams 6bc713bc2e feat: add additional parameters for QR code generator app (#9177)
* fix: add i18n key

* fix: add additional parameters

* fix: query params

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-01 10:13:54 +00:00
Hariom Balhara b1019d6bab fix: [Hotfix]No phone number is valid (#9258)
* Fix invalid phone number always

* Fix countrycode detection
2023-06-01 09:47:18 +00:00
Nafees Nazik 268f1872e4 fix: layout effect hook warning (#9226)
* feat: add isomorphic hook

* feat: use useIsomorphicLayoutEffect hook
2023-06-01 09:45:24 +00:00
Efraín RochínandKeith Williams 60d439cc32 prevents revalidation errors from reaching the when editing an event type (#9191)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-06-01 09:16:30 +00:00
Crowdin Bot 36d201ac20 New Crowdin translations by Github Action 2023-06-01 08:01:31 +00:00
d6644bc232 feat: Customise JITSI Url path (#8835)
* Closes #1847 - adds an environment variable `JITSI_HOST_URL` to configure the Host URL for self hosted instances

* Closes #1847 - adds an environment variable `JITSI_HOST_URL` to configure the Host URL for self hosted instances

* Closes #1847 - adds an environment variable `JITSI_HOST_URL` to configure the Host URL for self hosted instances

* Replace Tab with spaces

* Add JITSI_HOST_URL env

* Remote Tabs for Spaces

* Support jitsi_host key

* Remove ENV, add Interface jitsi host URL and fallback

* Fix linting issues

* Clean code

* add missing await

* Add configurable meeting slug

* Refactor to jitsiHost, remove slug generator

* zod default doesnt work right now. So remove it

* Add replacement values and url encoding

* Change Placeholder to unified values.

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-06-01 07:48:01 +00:00
alannnc 7a9b893931 Add startOf endOf day for date range (#9252) 2023-05-31 22:12:04 +00:00
Crowdin Bot 235c314e68 New Crowdin translations by Github Action 2023-05-31 21:02:11 +00:00
570 changed files with 13165 additions and 8429 deletions
-6
View File
@@ -106,12 +106,6 @@ LARK_OPEN_APP_ID=""
LARK_OPEN_APP_SECRET=""
LARK_OPEN_VERIFICATION_TOKEN=""
# - WEB3
# Used for the Web3 plugin
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/rainbow/README.md
ALCHEMY_API_KEY=""
INFURA_API_KEY=""
# - SALESFORCE
# Used for the Salesforce (Sales Cloud) app
SALESFORCE_CONSUMER_KEY=""
+28
View File
@@ -5,6 +5,7 @@
# - SHARED
# - NEXTAUTH
# - E-MAIL SETTINGS
# - ORGANIZATIONS
# - LICENSE (DEPRECATED) ************************************************************************************
# https://github.com/calcom/cal.com/blob/main/LICENSE
@@ -22,6 +23,8 @@ CALCOM_LICENSE_KEY=
# - DATABASE ************************************************************************************************
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
# Uncomment to enable a dedicated connection pool for Prisma using Prisma Data Proxy
# Cold boots will be faster and you'll be able to scale your DB independently of your app.
@@ -32,6 +35,8 @@ PRISMA_GENERATE_DATAPROXY=
# ***********************************************************************************************************
# - SHARED **************************************************************************************************
# Set this to http://app.cal.local:3000 if you want to enable organizations, and
# check variable ORGANIZATIONS_ENABLED at the bottom of this file
NEXT_PUBLIC_WEBAPP_URL='http://localhost:3000'
# Change to 'http://localhost:3001' if running the website simultaneously
NEXT_PUBLIC_WEBSITE_URL='http://localhost:3000'
@@ -183,3 +188,26 @@ CSP_POLICY=
EDGE_CONFIG=
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
# - ORGANIZATIONS *******************************************************************************************
# Enable Organizations non-prod domain setup, works in combination with organizations feature flag
# This is mainly needed locally, because for orgs to work a full domain name needs to point
# to the app, i.e. app.cal.local instead of using localhost, which is very disruptive
#
# This variable should only be set to 1 or true if you are in a non-prod environment and you want to
# use organizations
ORGANIZATIONS_ENABLED=
# Vercel Config to create subdomains for organizations
# Get it from https://vercel.com/<TEAM_OR_USER_NAME>/<PROJECT_SLUG>/settings
PROJECT_ID_VERCEL=
# Get it from: https://vercel.com/teams/<TEAM_SLUG>/settings
TEAM_ID_VERCEL=
# Get it from: https://vercel.com/account/tokens
AUTH_BEARER_TOKEN_VERCEL=
#Enables New booker for Embed only
NEW_BOOKER_ENABLED_FOR_EMBED=0
#Enables New booker for All but Embed requests
NEW_BOOKER_ENABLED_FOR_NON_EMBED=0
+3 -4
View File
@@ -8,8 +8,6 @@ Fixes # (issue)
Loom Video: https://www.loom.com/
-->
**Environment**: Staging(main branch) / Production
## Type of change
<!-- Please delete bullets that are not relevant. -->
@@ -27,13 +25,14 @@ Fixes # (issue)
- [ ] Test A
- [ ] 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 performed a self-review of my own code and corrected any misspellings
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my PR needs changes to the documentation
- I haven't checked if my changes generate no new warnings
+9
View File
@@ -40,6 +40,14 @@ runs:
restore-keys: |
yarn-download-cache-
# Invalidated on yarn.lock changes
- name: Restore node_modules
id: yarn-nm-cache
uses: buildjet/cache@v3
with:
path: "**/node_modules/"
key: ${{ runner.os }}-yarn-nm-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
# Invalidated on yarn.lock changes
- name: Restore yarn install state
id: yarn-install-state-cache
@@ -58,5 +66,6 @@ runs:
YARN_ENABLE_IMMUTABLE_INSTALLS: "false" # So it doesn't try to remove our private submodule deps
YARN_ENABLE_GLOBAL_CACHE: "false" # Use local cache folder to keep downloaded archives
YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz # Very small speedup when lock does not change
YARN_NM_MODE: "hardlinks-local" # Reduce node_modules size
# Other environment variables
HUSKY: "0" # By default do not run HUSKY install
@@ -0,0 +1,16 @@
name: Add PRs to project Reviewing PRs
on:
pull_request:
types:
- opened
jobs:
add-PR-to-project:
name: Add PRs to project Reviewing PRs
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.1.0
with:
project-url: https://github.com/orgs/calcom/projects/11
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
+64 -16
View File
@@ -1,27 +1,75 @@
name: "Apply issue labels to PR"
on:
pull_request:
types: [opened]
pull_request_target:
types:
- opened
jobs:
label_on_pr:
runs-on: ubuntu-latest
permissions:
contents: none
issues: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 16
- name: Apply labels from linked issue to PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_DATA: ${{ toJson(github.event.pull_request) }}
run: node ./.github/workflows/scripts/apply-issue-labels-to-pr.ts
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
async function getLinkedIssues(owner, repo, prNumber) {
const query = `query GetLinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $prNumber) {
closingIssuesReferences(first: 10) {
nodes {
number
labels(first: 10) {
nodes {
name
}
}
}
}
}
}
}`;
const variables = {
owner: owner,
repo: repo,
prNumber: prNumber,
};
const result = await github.graphql(query, variables);
return result.repository.pullRequest.closingIssuesReferences.nodes;
}
const pr = context.payload.pull_request;
const linkedIssues = await getLinkedIssues(
context.repo.owner,
context.repo.repo,
pr.number
);
const labelsToAdd = new Set();
for (const issue of linkedIssues) {
if (issue.labels && issue.labels.nodes) {
for (const label of issue.labels.nodes) {
labelsToAdd.add(label.name);
}
}
}
if (labelsToAdd.size) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: Array.from(labelsToAdd),
});
}
+3 -1
View File
@@ -25,7 +25,9 @@ jobs:
- uses: ./.github/actions/cache-db
- uses: ./.github/actions/cache-build
- name: Run Tests
run: yarn test-e2e:embed-react
run: |
yarn test-e2e:embed-react
yarn workspace @calcom/embed-react packaged:tests
env:
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
- name: Upload Test Results
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- name: Save Code Linting Reports
- name: Run Linting with Reports
run: yarn lint:report
continue-on-error: true
+1
View File
@@ -16,6 +16,7 @@ concurrency:
jobs:
changes:
name: Detect changes
runs-on: buildjet-4vcpu-ubuntu-2204
permissions:
pull-requests: read
@@ -1,139 +0,0 @@
const https = require("https");
async function applyLabelFromLinkedIssueToPR(pr, token) {
// Get the labels from issues linked to the PR
const query = `
query GetLinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $prNumber) {
closingIssuesReferences(first: 10) {
nodes {
number
labels(first: 10) {
nodes {
name
}
}
}
}
}
}
}
`;
const graphqlData = JSON.stringify({
query,
variables: {
owner: pr.base.repo.owner.login,
repo: pr.base.repo.name,
prNumber: pr.number,
},
});
const requestOptions = {
hostname: "api.github.com",
path: "/graphql",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": graphqlData.length,
Authorization: "Bearer " + token,
"User-Agent": "Node.js",
},
};
// Use the native Node.js request library to make the request
let linkedIssues;
linkedIssues = await new Promise((resolve, reject) => {
const request = https.request(requestOptions, (response) => {
let responseBody = "";
response.on("data", (chunk) => {
responseBody += chunk;
});
response.on("end", () => {
resolve(JSON.parse(responseBody)?.data?.repository?.pullRequest?.closingIssuesReferences?.nodes);
});
});
request.on("error", (error) => {
reject(error);
});
request.write(graphqlData);
request.end();
});
if (!linkedIssues || linkedIssues.length === 0) {
console.log("No issue linked.");
return;
}
// Iterate over linked issues and apply labels to PR
for (const issue of linkedIssues) {
const labels = issue?.labels?.nodes?.map((label) => label.name);
if (!labels || labels.length === 0) {
console.log(`No labels found on the linked issue #${issue.number}.`);
continue;
}
const labelsData = JSON.stringify({
labels: labels,
});
const requestOptions = {
hostname: "api.github.com",
path: `/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/issues/${pr.number}/labels`,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": labelsData.length,
Authorization: "Bearer " + token,
"User-Agent": "Node.js",
},
};
let labelResult;
labelResult = await new Promise((resolve, reject) => {
const request = https.request(requestOptions, (response) => {
let responseBody = "";
response.on("data", (chunk) => {
responseBody += chunk;
});
response.on("data", () => {});
response.on("end", () => {
resolve(JSON.parse(responseBody));
});
});
request.on("error", (error) => {
reject(error);
});
request.write(labelsData);
request.end();
});
if (labelResult.message) {
console.log(`Error labelling PR: ${labelResult.message}`);
continue;
}
console.log(
`Applied labels: ${labels.join(", ")} to PR #${pr.number} from linked issue #${issue.number}`
);
}
}
// Pass the PR data and GitHub token to the script
(async () => {
if (!process.env.PR_DATA) {
console.log("No PR data found.");
return;
}
const prData = JSON.parse(process.env.PR_DATA);
const token = process.env.GITHUB_TOKEN;
await applyLabelFromLinkedIssueToPR(prData, token);
})();
+9 -1
View File
@@ -201,6 +201,14 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
1. Set a 32 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
In a development environment, run:
```sh
yarn workspace @calcom/prisma db-migrate
```
In a production environment, run:
```sh
yarn workspace @calcom/prisma db-deploy
```
@@ -416,7 +424,7 @@ Make sure to complete section "Obtaining the Google API Credentials". After the
following
1. Add extra redirect URL `<Cal.com URL>/api/auth/callback/google`
1. Under 'OAuth concent screen', click "PUBLISH APP"
1. Under 'OAuth consent screen', click "PUBLISH APP"
### Obtaining Microsoft Graph Client ID and Secret
+3
View File
@@ -2,6 +2,7 @@ import type { NextApiRequest } from "next";
import { z } from "zod";
import { getUserAvailability } from "@calcom/core/getUserAvailability";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { availabilityUserSelect } from "@calcom/prisma";
@@ -119,8 +120,10 @@ const availabilitySchema = z
async function handler(req: NextApiRequest) {
const { prisma, isAdmin, userId: reqUserId } = req;
const { username, userId, eventTypeId, dateTo, dateFrom, teamId } = availabilitySchema.parse(req.query);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.host ?? "");
if (!teamId)
return getUserAvailability({
orgSlug: isValidOrgDomain ? currentOrgDomain || undefined : undefined,
username,
dateFrom,
dateTo,
+1 -1
View File
@@ -33,7 +33,7 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
* type: integer
* example: [2, 3, 4]
* - in: query
* name: attendeeEmails
* name: attendeeEmail
* required: false
* schema:
* oneOf:
-1
View File
@@ -26,7 +26,6 @@ import { defaultResponder } from "@calcom/lib/server";
* required:
* - eventTypeId
* - start
* - end
* - name
* - email
* - timeZone
+1 -1
View File
@@ -50,6 +50,6 @@
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.2.1",
"typescript": "^4.9.4",
"vite": "^2.9.15"
"vite": "^4.1.2"
}
}
+4 -2
View File
@@ -235,6 +235,7 @@
*/
--cal-brand:#111827;
--cal-brand-emphasis:#101010;
--cal-brand-text: white;
}
.dark {
/* background */
@@ -274,7 +275,8 @@
/* Brand shenanigans
-> These will be computed for the users theme at runtime.
*/
--cal-brand: #111827;
--cal-brand-emphasis: #101010;
--cal-brand: white;
--cal-brand-emphasis: #e1e1e1;
--cal-brand-text: black;
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ export const SnippedGenerator = {
// extend some internals to gain information about current path, method and spec in the generator function metioned later
spec: {
wrapSelectors: {
requestFor: (ori, system) => (state, path, method) => {
requestFor: (ori) => (state, path, method) => {
return ori(path, method)
?.set("spec", state.get("json", {}))
?.setIn(["oasPathMethod", "path"], path)
+1
View File
@@ -60,6 +60,7 @@ export default function AppListCard(props: AppListCardProps) {
timeoutRef.current = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
+45 -8
View File
@@ -8,8 +8,11 @@ import { createRef, forwardRef, useRef, useState } from "react";
import type { ControlProps } from "react-select";
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 { useLocale } from "@calcom/lib/hooks/useLocale";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import {
Button,
Dialog,
@@ -41,12 +44,13 @@ type PreviewState = {
height: string;
};
theme: Theme;
floatingPopup: Record<string, string>;
floatingPopup: Record<string, string | boolean | undefined>;
elementClick: Record<string, string>;
palette: {
brandColor: string;
};
hideEventTypeDetails: boolean;
layout: BookerLayouts;
};
const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl"];
@@ -129,11 +133,13 @@ const getEmbedUIInstructionString = ({
theme,
brandColor,
hideEventTypeDetails,
layout,
}: {
apiName: string;
theme?: string;
brandColor: string;
hideEventTypeDetails: boolean;
layout?: string;
}) => {
theme = theme !== "auto" ? theme : undefined;
return getInstructionString({
@@ -147,6 +153,7 @@ const getEmbedUIInstructionString = ({
},
},
hideEventTypeDetails: hideEventTypeDetails,
layout,
},
});
};
@@ -260,6 +267,7 @@ const getEmbedTypeSpecificString = ({
theme: PreviewState["theme"];
brandColor: string;
hideEventTypeDetails: boolean;
layout?: BookerLayout;
};
if (embedFramework === "react") {
uiInstructionStringArg = {
@@ -267,6 +275,7 @@ const getEmbedTypeSpecificString = ({
theme: previewState.theme,
brandColor: previewState.palette.brandColor,
hideEventTypeDetails: previewState.hideEventTypeDetails,
layout: previewState.layout,
};
} else {
uiInstructionStringArg = {
@@ -274,6 +283,7 @@ const getEmbedTypeSpecificString = ({
theme: previewState.theme,
brandColor: previewState.palette.brandColor,
hideEventTypeDetails: previewState.hideEventTypeDetails,
layout: previewState.layout,
};
}
if (!frameworkCodes[embedType]) {
@@ -626,6 +636,7 @@ const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme;
const ChooseEmbedTypesDialogContent = () => {
const { t } = useLocale();
const router = useRouter();
return (
<DialogContent className="rounded-lg p-10" type="creation" size="lg">
<div className="mb-2">
@@ -671,6 +682,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
const router = useRouter();
const iframeRef = useRef<HTMLIFrameElement>(null);
const dialogContentRef = useRef<HTMLDivElement>(null);
const flags = useFlagMap();
const isBookerLayoutsEnabled = flags["booker-layouts"] === true;
const s = (href: string) => {
const searchParams = new URLSearchParams(router.asPath.split("?")[1] || "");
@@ -691,7 +704,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true);
const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
const [previewState, setPreviewState] = useState({
const [previewState, setPreviewState] = useState<PreviewState>({
inline: {
width: "100%",
height: "100%",
@@ -703,6 +716,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
palette: {
brandColor: "#000000",
},
layout: BookerLayouts.MONTH_VIEW,
});
const close = () => {
@@ -765,6 +779,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
arg: {
theme: previewState.theme,
hideEventTypeDetails: previewState.hideEventTypeDetails,
layout: previewState.layout,
styles: {
branding: {
...previewState.palette,
@@ -798,6 +813,12 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
{ value: Theme.light, label: "Light Theme" },
];
const layoutOptions = [
{ value: BookerLayouts.MONTH_VIEW, label: t("bookerlayout_month_view") },
{ value: BookerLayouts.WEEK_VIEW, label: t("bookerlayout_week_view") },
{ value: BookerLayouts.COLUMN_VIEW, label: t("bookerlayout_column_view") },
];
const FloatingPopupPositionOptions = [
{
value: "bottom-right",
@@ -1070,6 +1091,27 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div>
</Label>
))}
{isBookerLayoutsEnabled && (
<Label className="mb-6">
<div className="mb-2">{t("layout")}</div>
<Select
className="w-full"
defaultValue={layoutOptions[0]}
onChange={(option) => {
if (!option) {
return;
}
setPreviewState((previewState) => {
return {
...previewState,
layout: option.value,
};
});
}}
options={layoutOptions}
/>
</Label>
)}
</div>
</CollapsibleContent>
</Collapsible>
@@ -1133,12 +1175,7 @@ export const EmbedDialog = () => {
const router = useRouter();
const embedUrl: string = router.query.embedUrl as string;
return (
<Dialog
name="embed"
clearQueryParamsOnClose={queryParamsForDialog}
onOpenChange={(open) => {
if (!open) window.resetEmbedStatus();
}}>
<Dialog name="embed" clearQueryParamsOnClose={queryParamsForDialog}>
{!router.query.embedType ? (
<ChooseEmbedTypesDialogContent />
) : (
-51
View File
@@ -1,51 +0,0 @@
import dynamic from "next/dynamic";
import type { Dispatch } from "react";
import { useState, useEffect } from "react";
import type { JSONObject } from "superjson/dist/types";
export type Gate = undefined | "rainbow"; // Add more like ` | "geolocation" | "payment"`
export type GateState = {
rainbowToken?: string;
};
type GateProps = {
children: React.ReactNode;
gates: Gate[];
appData: JSONObject;
dispatch: Dispatch<Partial<GateState>>;
};
const RainbowGate = dynamic(() => import("@calcom/app-store/rainbow/components/RainbowKit"));
// To add a new Gate just add the gate logic to the switch statement
const Gates: React.FC<GateProps> = ({ children, gates, appData, dispatch }) => {
const [rainbowToken, setRainbowToken] = useState<string>();
useEffect(() => {
dispatch({ rainbowToken });
}, [rainbowToken, dispatch]);
let gateWrappers = <>{children}</>;
// Recursively wraps the `gateWrappers` with new gates allowing for multiple gates
for (const gate of gates) {
switch (gate) {
case "rainbow":
if (appData.blockchainId && appData.smartContractAddress && !rainbowToken) {
gateWrappers = (
<RainbowGate
setToken={setRainbowToken}
chainId={appData.blockchainId as number}
tokenAddress={appData.smartContractAddress as string}>
{gateWrappers}
</RainbowGate>
);
}
}
}
return gateWrappers;
};
export default Gates;
+26 -2
View File
@@ -1,10 +1,13 @@
import { DefaultSeo } from "next-seo";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
import Head from "next/head";
import Script from "next/script";
import "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { buildCanonical } from "@calcom/lib/next-seo.config";
import type { AppProps } from "@lib/app-providers";
import AppProviders from "@lib/app-providers";
@@ -13,7 +16,7 @@ import { seoConfig } from "@lib/config/next-seo.config";
import I18nLanguageHandler from "@components/I18nLanguageHandler";
export interface CalPageWrapper {
(props?: any): JSX.Element;
(props?: AppProps): JSX.Element;
PageWrapper?: AppProps["Component"]["PageWrapper"];
}
@@ -49,9 +52,30 @@ function PageWrapper(props: AppProps) {
};
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page);
// Canonical: Check if the URL is from cal.com so that we set the canonical conditionally
const isCalcom =
WEBAPP_URL &&
(new URL(WEBAPP_URL).hostname.endsWith("cal.com") || new URL(WEBAPP_URL).hostname.endsWith("cal.dev"));
const path = router.asPath;
return (
<AppProviders {...providerProps}>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
</Head>
<DefaultSeo
// Set canonical to https://cal.com or self-hosted URL
canonical={
isCalcom
? buildCanonical({ path, origin: "https://cal.com" }) // cal.com & .dev
: buildCanonical({ path, origin: WEBAPP_URL }) // self-hosted
}
{...seoConfig.defaultNextSeo}
/>
<I18nLanguageHandler />
<Script
nonce={nonce}
@@ -1,3 +1,5 @@
import type { FunctionComponent, SVGProps } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -55,7 +57,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
<InstallAppButton
type={data.type}
render={(installProps) => {
const props = { ...installProps } as any;
const props = { ...installProps } as FunctionComponent<SVGProps<SVGSVGElement>>;
return (
<DropdownItem {...props} color="minimal" type="button">
<span className="flex items-center gap-x-2">
@@ -2,60 +2,12 @@ import type { ComponentProps } from "react";
import React from "react";
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
import type { InstalledAppVariants } from "@calcom/app-store/utils";
import Shell from "@calcom/features/shell/Shell";
import { trpc } from "@calcom/trpc/react";
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
import { BarChart, Calendar, CreditCard, Grid, Share2, Video } from "@calcom/ui/components/icon";
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{
name: "calendar",
href: "/apps/installed/calendar",
icon: Calendar,
},
{
name: "conferencing",
href: "/apps/installed/conferencing",
icon: Video,
},
{
name: "payment",
href: "/apps/installed/payment",
icon: CreditCard,
},
{
name: "automation",
href: "/apps/installed/automation",
icon: Share2,
},
{
name: "analytics",
href: "/apps/installed/analytics",
icon: BarChart,
},
{
name: "other",
href: "/apps/installed/other",
icon: Grid,
},
];
export default function InstalledAppsLayout({
children,
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const variant: (typeof InstalledAppVariants)[number] = "payment";
const query = trpc.viewer.integrations.useQuery({
variant,
onlyInstalled: true,
});
let actualTabs = tabs;
if (query.data?.items.length === 0) {
actualTabs = tabs.filter((tab) => tab.name !== variant);
}
return (
<Shell {...rest} hideHeadingOnMobile>
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
+15 -15
View File
@@ -26,24 +26,24 @@ export default function TwoFactor({ center = true }) {
return (
<div className={center ? "mx-auto !mt-0 max-w-sm" : "!mt-0 max-w-sm"}>
<Label className="mt-4"> {t("2fa_code")}</Label>
<Label className="mt-4">{t("2fa_code")}</Label>
<p className="text-subtle mb-4 text-sm">{t("2fa_enabled_instructions")}</p>
<input hidden type="hidden" value={value} {...methods.register("totpCode")} />
<input type="hidden" value={value} {...methods.register("totpCode")} />
<div className="flex flex-row justify-between">
<Input
className={className}
name="2fa1"
inputMode="decimal"
{...digits[0]}
autoFocus
autoComplete="one-time-code"
/>
<Input className={className} name="2fa2" inputMode="decimal" {...digits[1]} />
<Input className={className} name="2fa3" inputMode="decimal" {...digits[2]} />
<Input className={className} name="2fa4" inputMode="decimal" {...digits[3]} />
<Input className={className} name="2fa5" inputMode="decimal" {...digits[4]} />
<Input className={className} name="2fa6" inputMode="decimal" {...digits[5]} />
{digits.map((digit, index) => (
<Input
key={`2fa${index}`}
className={className}
name={`2fa${index + 1}`}
inputMode="decimal"
{...digit}
autoFocus={index === 0}
autoComplete="one-time-code"
/>
))}
</div>
</div>
);
@@ -24,7 +24,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
}
const translateAbleKeys = [
"attendee_in_person",
"in_person_attendee_address",
"in_person",
"attendee_phone_number",
"link_meeting",
@@ -28,7 +28,6 @@ type AvailableTimesProps = {
bookingAttendees?: number | null;
slots?: Slot[];
isLoading: boolean;
ethSignature?: string;
duration: number;
};
@@ -43,7 +42,6 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
onTimeFormatChange,
seatsPerTimeSlot,
bookingAttendees,
ethSignature,
duration,
}) => {
const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation();
@@ -119,7 +117,6 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
timeFormat,
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
...(ethSignature ? { ethSignature } : {}),
},
};
@@ -164,7 +161,8 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
brand === "#fff" || brand === "#ffffff" ? "" : ""
)}
onClick={() => reserveSlot(slot)}
data-testid="time">
data-testid="time"
data-disabled="false">
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{!!seatsPerTimeSlot && (
<p
@@ -83,7 +83,9 @@ const BookingDescription: FC<Props> = (props) => {
size="sm"
truncateAfter={3}
/>
<h2 className="text-default mt-1 mb-2 break-words text-sm font-medium ">{profile.name}</h2>
<h2 className="text-default mt-1 mb-2 break-words text-sm font-medium ">
{eventType.team?.parent?.name} {profile.name}
</h2>
<h1 className="font-cal text-emphasis mb-6 break-words text-2xl font-semibold leading-none">
{eventType.title}
</h1>
@@ -59,6 +59,7 @@ export default function CancelBooking(props: Props) {
<div className="mt-5 sm:mt-6">
<label className="text-default font-medium">{t("cancellation_reason")}</label>
<TextArea
data-testid="cancel_reason"
ref={cancelBookingRef}
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
@@ -75,7 +76,7 @@ export default function CancelBooking(props: Props) {
{t("nevermind")}
</Button>
<Button
data-testid="cancel"
data-testid="confirm_cancel"
onClick={async () => {
setLoading(true);
@@ -80,7 +80,6 @@ export const SlotPicker = ({
seatsPerTimeSlot,
bookingAttendees,
weekStart = 0,
ethSignature,
}: {
eventType: Pick<
EventType & { metadata: z.infer<typeof EventTypeMetaDataSchema> },
@@ -94,7 +93,6 @@ export const SlotPicker = ({
recurringEventCount?: number;
users: string[];
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
ethSignature?: string;
}) => {
const [selectedDate, setSelectedDate] = useState<Dayjs>();
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
@@ -193,7 +191,6 @@ export const SlotPicker = ({
seatsPerTimeSlot={seatsPerTimeSlot}
bookingAttendees={bookingAttendees}
recurringCount={recurringEventCount}
ethSignature={ethSignature}
duration={parseInt(duration)}
/>
</>
@@ -1,10 +1,9 @@
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import { useEffect, useMemo, useReducer, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { z } from "zod";
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
import { getEventTypeAppData } from "@calcom/app-store/utils";
import dayjs from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
@@ -27,8 +26,6 @@ import { CreditCard, User, RefreshCcw } from "@calcom/ui/components/icon";
import { timeZone as localStorageTimeZone } from "@lib/clock";
import type { Gate, GateState } from "@components/Gates";
import Gates from "@components/Gates";
import BookingDescription from "@components/booking/BookingDescription";
import { SlotPicker } from "@components/booking/SlotPicker";
@@ -89,14 +86,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
setIs24hClockInLocalStorage(is24Hours);
};
const [gateState, gateDispatcher] = useReducer(
(state: GateState, newState: Partial<GateState>) => ({
...state,
...newState,
}),
{}
);
useEffect(() => {
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
}, []);
@@ -124,20 +113,11 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
);
const paymentAppData = getPaymentAppData(eventType);
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
// Define conditional gates here
const gates = [
// Rainbow gate is only added if the event has both a `blockchainId` and a `smartContractAddress`
rainbowAppData && rainbowAppData.blockchainId && rainbowAppData.smartContractAddress
? ("rainbow" as Gate)
: undefined,
];
const { data: bookingAttendees } = trpc.viewer.bookings.getBookingAttendees.useQuery(
{
seatReferenceUid: rescheduleUid,
@@ -148,7 +128,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
);
return (
<Gates gates={gates} appData={rainbowAppData} dispatch={gateDispatcher}>
<>
<HeadSeo
title={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title} | ${profile.name}`}
description={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title}`}
@@ -296,7 +276,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
bookingAttendees={bookingAttendees || undefined}
recurringEventCount={recurringEventCount}
ethSignature={gateState.rainbowToken}
/>
</div>
</div>
@@ -306,7 +285,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
</main>
</div>
<Toaster position="bottom-right" />
</Gates>
</>
);
};
@@ -4,7 +4,7 @@ import { useSession } from "next-auth/react";
import dynamic from "next/dynamic";
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useMemo, useReducer, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useForm, useFormContext } from "react-hook-form";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
@@ -12,7 +12,6 @@ import { z } from "zod";
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
import type { EventLocationType } from "@calcom/app-store/locations";
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
import { getEventTypeAppData } from "@calcom/app-store/utils";
import type { LocationObject } from "@calcom/core/location";
import dayjs from "@calcom/dayjs";
import {
@@ -51,8 +50,6 @@ import { AlertTriangle, Calendar, RefreshCw, User } from "@calcom/ui/components/
import { timeZone } from "@lib/clock";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import type { Gate, GateState } from "@components/Gates";
import Gates from "@components/Gates";
import BookingDescription from "@components/booking/BookingDescription";
import type { BookPageProps } from "../../../pages/[user]/book";
@@ -224,13 +221,6 @@ const BookingPage = ({
const { data: session } = useSession();
const isBackgroundTransparent = useIsBackgroundTransparent();
const telemetry = useTelemetry();
const [gateState, gateDispatcher] = useReducer(
(state: GateState, newState: Partial<GateState>) => ({
...state,
...newState,
}),
{}
);
const { timezone } = useTimePreferences();
@@ -488,7 +478,6 @@ const BookingPage = ({
metadata,
hasHashedBookingLink,
hashedLink,
ethSignature: gateState.rainbowToken,
}));
recurringMutation.mutate(recurringBookings);
} else {
@@ -506,25 +495,15 @@ const BookingPage = ({
metadata,
hasHashedBookingLink,
hashedLink,
ethSignature: gateState.rainbowToken,
seatReferenceUid: router.query.seatReferenceUid as string,
});
}
};
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
// Define conditional gates here
const gates = [
// Rainbow gate is only added if the event has both a `blockchainId` and a `smartContractAddress`
rainbowAppData && rainbowAppData.blockchainId && rainbowAppData.smartContractAddress
? ("rainbow" as Gate)
: undefined,
];
return (
<Gates gates={gates} appData={rainbowAppData} dispatch={gateDispatcher}>
<>
<Head>
<title>
{rescheduleUid
@@ -686,7 +665,7 @@ const BookingPage = ({
</div>
</main>
<Toaster position="bottom-right" />
</Gates>
</>
);
};
@@ -57,12 +57,15 @@ const LocationInput = (props: {
<Input {...locationFormMethods.register(eventLocationType.variable)} type="text" {...remainingProps} />
);
} else if (eventLocationType?.organizerInputType === "phone") {
const { defaultValue, ...rest } = remainingProps;
return (
<Controller
name={eventLocationType.variable}
control={control}
defaultValue={defaultValue}
render={({ field: { onChange, value } }) => {
return <PhoneInput onChange={onChange} value={value} {...remainingProps} />;
return <PhoneInput onChange={onChange} value={value} {...rest} />;
}}
/>
);
@@ -88,6 +91,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
useEffect(() => {
if (selection) {
locationFormMethods.setValue("locationType", selection?.value);
if (selection?.address) {
locationFormMethods.setValue("locationAddress", selection?.address);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selection]);
@@ -149,12 +155,25 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
name: "locationType",
});
const selectedAddrValue = useWatch({
control: locationFormMethods.control,
name: "locationAddress",
});
const eventLocationType = getEventLocationType(selectedLocation);
const defaultLocation = defaultValues?.find(
(location: { type: EventLocationType["type"] }) => location.type === eventLocationType?.type
(location: { type: EventLocationType["type"]; address?: string }) => {
if (location.type === LocationType.InPerson) {
return location.type === eventLocationType?.type && location.address === selectedAddrValue;
} else {
return location.type === eventLocationType?.type;
}
}
);
console.log(defaultLocation);
const LocationOptions = (() => {
if (eventLocationType && eventLocationType.organizerInputType && LocationInput) {
if (!eventLocationType.variable) {
@@ -55,7 +55,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
</p>
<TextArea
data-testid="reschedule_reason"
name={t("reschedule_reason")}
name={t("reason_for_reschedule")}
value={rescheduleReason}
onChange={(e) => setRescheduleReason(e.target.value)}
className="mb-5 sm:mb-6"
@@ -16,6 +16,7 @@ import {
allowDisablingHostConfirmationEmails,
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import { classNames } from "@calcom/lib";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -65,7 +66,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
const noShowFeeEnabled = eventType.metadata.apps?.stripe?.paymentOption === "HOLD";
const noShowFeeEnabled = eventType.metadata?.apps?.stripe?.paymentOption === "HOLD";
useEffect(() => {
!hashedUrl && setHashedUrl(generateHashedLink(eventType.users[0]?.id ?? team?.id));
@@ -159,6 +160,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}
/>
</div>
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
<div>
<BookerLayoutSelector fallbackToUserSettings />
</div>
<hr className="border-subtle" />
<FormBuilder
title={t("booking_questions_title")}
@@ -104,9 +104,9 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
</div>
</div>
{!shouldLockDisableProps("apps").disabled && (
<div>
<div className="bg-muted rounded-md p-8">
{!isLoading && notInstalledApps?.length ? (
<h2 className="text-emphasis my-2 text-lg font-semibold">{t("available_apps")}</h2>
<h2 className="text-emphasis text-lg font-semibold">{t("available_apps")}</h2>
) : null}
<div className="before:border-0">
{notInstalledApps?.map((app) => (
@@ -115,7 +115,7 @@ export const EventSetupTab = (
const [showLocationModal, setShowLocationModal] = useState(false);
const [editingLocationType, setEditingLocationType] = useState<string>("");
const [selectedLocation, setSelectedLocation] = useState<LocationOption | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata?.multipleDuration);
const locationOptions = props.locationOptions.filter((option) => {
return !team ? option.label !== "Conferencing" : true;
@@ -136,9 +136,17 @@ export const EventSetupTab = (
selectedMultipleDuration.find((opt) => opt.value === eventType.length) ?? null
);
const openLocationModal = (type: EventLocationType["type"]) => {
const openLocationModal = (type: EventLocationType["type"], address = "") => {
const option = getLocationFromType(type, locationOptions);
setSelectedLocation(option);
if (option && option.value === LocationType.InPerson) {
const inPersonOption = {
...option,
address,
};
setSelectedLocation(inPersonOption);
} else {
setSelectedLocation(option);
}
setShowLocationModal(true);
};
@@ -298,10 +306,14 @@ export const EventSetupTab = (
onClick={() => {
locationFormMethods.setValue("locationType", location.type);
locationFormMethods.unregister("locationLink");
locationFormMethods.unregister("locationAddress");
if (location.type === LocationType.InPerson) {
locationFormMethods.setValue("locationAddress", location.address);
} else {
locationFormMethods.unregister("locationAddress");
}
locationFormMethods.unregister("locationPhoneNumber");
setEditingLocationType(location.type);
openLocationModal(location.type);
openLocationModal(location.type, location.address);
}}
aria-label={t("edit")}
className="hover:text-emphasis text-subtle mr-1 p-1">
@@ -517,7 +529,18 @@ export const EventSetupTab = (
defaultValues={formMethods.getValues("locations")}
selection={
selectedLocation
? { value: selectedLocation.value, label: t(selectedLocation.label), icon: selectedLocation.icon }
? selectedLocation.address
? {
value: selectedLocation.value,
label: t(selectedLocation.label),
icon: selectedLocation.icon,
address: selectedLocation.address,
}
: {
value: selectedLocation.value,
label: t(selectedLocation.label),
icon: selectedLocation.icon,
}
: undefined
}
setSelectedLocation={setSelectedLocation}
@@ -75,8 +75,7 @@ function getNavigation(props: {
installedAppsNumber: number;
availability: AvailabilityOption | undefined;
}) {
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber, availability } =
props;
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
const duration =
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
@@ -178,6 +177,7 @@ function EventTypeSingleLayout({
enabledWorkflowsNumber,
availability,
});
navigation.splice(1, 0, {
name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`,
@@ -185,7 +185,7 @@ function EventTypeSingleLayout({
info:
isManagedEventType || isChildrenManagedEventType
? eventType.schedule === null
? "Member's default schedule"
? "members_default_schedule"
: isChildrenManagedEventType
? `${
eventType.scheduleName
@@ -193,7 +193,7 @@ function EventTypeSingleLayout({
: `default_schedule_name`
}`
: eventType.scheduleName ?? `default_schedule_name`
: `default_schedule_name`,
: eventType.scheduleName ?? `default_schedule_name`,
});
// If there is a team put this navigation item within the tabs
if (team) {
@@ -220,7 +220,18 @@ function EventTypeSingleLayout({
});
}
return navigation;
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team, availability]);
}, [
t,
eventType,
enabledAppsNumber,
installedAppsNumber,
enabledWorkflowsNumber,
availability,
isManagedEventType,
isChildrenManagedEventType,
team,
formMethods,
]);
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
eventType.slug
@@ -236,7 +247,7 @@ function EventTypeSingleLayout({
heading={eventType.title}
CTA={
<div className="flex items-center justify-end">
{!eventType.metadata.managedEventConfig && (
{!eventType.metadata?.managedEventConfig && (
<>
<div
className={classNames(
@@ -379,7 +390,7 @@ function EventTypeSingleLayout({
<Button
className="ml-4 lg:ml-0"
type="submit"
loading={formMethods.formState.isSubmitting || isUpdateMutationLoading}
loading={isUpdateMutationLoading}
data-testid="update-eventtype"
form="event-type-form">
{t("save")}
@@ -39,6 +39,7 @@ export default function RequiresConfirmationController({
if (!requiresConfirmation) {
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [requiresConfirmation]);
const { shouldLockDisableProps } = useLockedFieldsManager(
@@ -15,7 +15,7 @@ interface IConnectCalendarsProps {
const ConnectedCalendars = (props: IConnectCalendarsProps) => {
const { nextStep } = props;
const queryConnectedCalendars = trpc.viewer.connectedCalendars.useQuery();
const queryConnectedCalendars = trpc.viewer.connectedCalendars.useQuery({ onboarding: true });
const { t } = useLocale();
const queryIntegrations = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
@@ -8,23 +8,17 @@ import { md } from "@calcom/lib/markdownIt";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import turndown from "@calcom/lib/turndownService";
import { trpc } from "@calcom/trpc/react";
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
import { Avatar } from "@calcom/ui";
import { Avatar, Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
type FormData = {
bio: string;
};
interface IUserProfileProps {
user: IOnboardingPageProps["user"];
}
const UserProfile = (props: IUserProfileProps) => {
const { user } = props;
const UserProfile = () => {
const [user] = trpc.viewer.me.useSuspenseQuery();
const { t } = useLocale();
const avatarRef = useRef<HTMLInputElement>(null!);
const avatarRef = useRef<HTMLInputElement>(null);
const { setValue, handleSubmit, getValues } = useForm<FormData>({
defaultValues: { bio: user?.bio || "" },
});
@@ -76,7 +70,7 @@ const UserProfile = (props: IUserProfileProps) => {
async function updateProfileHandler(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const enteredAvatar = avatarRef.current.value;
const enteredAvatar = avatarRef.current?.value;
mutation.mutate({
avatar: enteredAvatar,
});
@@ -127,14 +121,16 @@ const UserProfile = (props: IUserProfileProps) => {
id="avatar-upload"
buttonMsg={t("add_profile_photo")}
handleAvatarChange={(newAvatar) => {
avatarRef.current.value = newAvatar;
if (avatarRef.current) {
avatarRef.current.value = newAvatar;
}
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
"value"
)?.set;
nativeInputValueSetter?.call(avatarRef.current, newAvatar);
const ev2 = new Event("input", { bubbles: true });
avatarRef.current.dispatchEvent(ev2);
avatarRef.current?.dispatchEvent(ev2);
updateProfileHandler(ev2 as unknown as FormEvent<HTMLFormElement>);
setImageSrc(newAvatar);
}}
@@ -13,15 +13,13 @@ import { ArrowRight } from "@calcom/ui/components/icon";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
interface IUserSettingsProps {
user: IOnboardingPageProps["user"];
nextStep: () => void;
}
const UserSettings = (props: IUserSettingsProps) => {
const { user, nextStep } = props;
const { nextStep } = props;
const [user] = trpc.viewer.me.useSuspenseQuery();
const { t } = useLocale();
const [selectedTimeZone, setSelectedTimeZone] = useState(dayjs.tz.guess());
const telemetry = useTelemetry();
@@ -69,7 +67,7 @@ const UserSettings = (props: IUserSettingsProps) => {
<form onSubmit={onSubmit}>
<div className="space-y-6">
{/* Username textfield */}
<UsernameAvailabilityField user={user} />
<UsernameAvailabilityField />
{/* Full name textfield */}
<div className="w-full">
+3 -2
View File
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import type { TeamWithMembers } from "@calcom/lib/server/queries/teams";
import { Avatar } from "@calcom/ui";
@@ -23,7 +24,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
return (
<Link key={member.id} href={{ pathname: `/${member.username}`, query: queryParamsToForward }}>
<div className="sm:min-w-80 sm:max-w-80 bg-default hover:bg-muted border-subtle group flex min-h-full flex-col space-y-2 rounded-md border p-4 hover:cursor-pointer">
<div className="sm:min-w-80 sm:max-w-80 bg-default hover:bg-muted border-subtle group flex min-h-full flex-col space-y-2 rounded-md border p-4 hover:cursor-pointer">
<Avatar
size="md"
alt={member.name || ""}
@@ -36,7 +37,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
<>
<div
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: md.render(member.bio || "") }}
dangerouslySetInnerHTML={{ __html: md.render(markdownToSafeHTML(member.bio)) }}
/>
</>
) : (
@@ -8,14 +8,12 @@ import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/ut
import { fetchUsername } from "@calcom/lib/fetchUsername";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { User } from "@calcom/prisma/client";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button, Dialog, DialogClose, DialogContent, Input, Label } from "@calcom/ui";
import { Star as StarSolid } from "@calcom/ui/components/icon";
import { Check, Edit2, ExternalLink } from "@calcom/ui/components/icon";
import { Check, Edit2, ExternalLink, Star as StarSolid } from "@calcom/ui/components/icon";
export enum UsernameChangeStatusEnum {
UPGRADE = "UPGRADE",
@@ -29,7 +27,6 @@ interface ICustomUsernameProps {
setInputUsernameValue: (value: string) => void;
onSuccessMutation?: () => void;
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
user: Pick<User, "username" | "metadata">;
readonly?: boolean;
}
@@ -57,8 +54,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
onSuccessMutation,
onErrorMutation,
readonly: disabled,
user,
} = props;
const [user] = trpc.viewer.me.useSuspenseQuery();
const [usernameIsAvailable, setUsernameIsAvailable] = useState(false);
const [markAsError, setMarkAsError] = useState(false);
const router = useRouter();
@@ -21,7 +21,7 @@ interface ICustomUsernameProps {
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
}
const UsernameTextfield = (props: ICustomUsernameProps) => {
const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.ComponentProps<typeof TextField>>) => {
const { t } = useLocale();
const {
currentUsername,
@@ -31,6 +31,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
usernameRef,
onSuccessMutation,
onErrorMutation,
...rest
} = props;
const [usernameIsAvailable, setUsernameIsAvailable] = useState(false);
const [markAsError, setMarkAsError] = useState(false);
@@ -79,7 +80,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
const ActionButtons = () => {
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
<div className="ms-2 me-2 mt-px flex flex-row space-x-2">
<div className="ms-2 me-2 flex flex-row space-x-2">
<Button
type="button"
onClick={() => setOpenDialogSaveUsername(true)}
@@ -116,9 +117,6 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
ref={usernameRef}
name="username"
value={inputUsernameValue}
addOnLeading={
<>{process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")}/</>
}
autoComplete="none"
autoCapitalize="none"
autoCorrect="none"
@@ -133,6 +131,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
setInputUsernameValue(event.target.value);
}}
data-testid="username-input"
{...rest}
/>
{currentUsername !== inputUsernameValue && (
<div className="absolute right-[2px] top-6 flex flex-row">
@@ -142,7 +141,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
</div>
)}
</div>
<div className="mt-5 hidden md:inline">
<div className="mt-7 hidden md:inline">
<ActionButtons />
</div>
</div>
@@ -2,9 +2,11 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import type { User } from "@calcom/prisma/client";
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -17,14 +19,24 @@ export const UsernameAvailability = IS_SELF_HOSTED ? UsernameTextfield : Premium
interface UsernameAvailabilityFieldProps {
onSuccessMutation?: () => void;
onErrorMutation?: (error: TRPCClientErrorLike<AppRouter>) => void;
user: Pick<User, "username" | "metadata">;
}
function useUserNamePrefix(organization: RouterOutputs["viewer"]["me"]["organization"]): string {
return organization
? organization.slug
? `${organization.slug}.${subdomainSuffix()}`
: organization.metadata && organization.metadata.requestedSlug
? `${organization.metadata.requestedSlug}.${subdomainSuffix()}`
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")
: process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "");
}
export const UsernameAvailabilityField = ({
onSuccessMutation,
onErrorMutation,
user,
}: UsernameAvailabilityFieldProps) => {
const router = useRouter();
const [user] = trpc.viewer.me.useSuspenseQuery();
const [currentUsernameState, setCurrentUsernameState] = useState(user.username || "");
const { username: usernameFromQuery, setQuery: setUsernameFromQuery } = useRouterQuery("username");
const { username: currentUsername, setQuery: setCurrentUsername } =
@@ -37,6 +49,8 @@ export const UsernameAvailabilityField = ({
},
});
const usernamePrefix = useUserNamePrefix(user.organization);
return (
<Controller
control={formMethods.control}
@@ -51,7 +65,7 @@ export const UsernameAvailabilityField = ({
setInputUsernameValue={onChange}
onSuccessMutation={onSuccessMutation}
onErrorMutation={onErrorMutation}
user={user}
addOnLeading={usernamePrefix}
/>
);
}}
@@ -26,12 +26,6 @@ export const CheckedSelect = ({
return (
<>
<Select
styles={{
option: (styles, { isDisabled }) => ({
...styles,
backgroundColor: isDisabled ? "#F5F5F5" : "inherit",
}),
}}
name={props.name}
placeholder={props.placeholder || t("select")}
isSearchable={false}
@@ -11,6 +11,7 @@ export type LocationOption = {
value: EventLocationType["type"];
icon?: string;
disabled?: boolean;
address?: string;
};
export type SingleValueLocationOption = SingleValue<LocationOption>;
+4 -2
View File
@@ -66,6 +66,7 @@ export function QueryCell<TData, TError extends ErrorLike>(
if ("empty" in opts && (query.data == null || (Array.isArray(query.data) && query.data.length === 0))) {
return opts.empty(query);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return opts.success(query as any);
}
@@ -88,9 +89,10 @@ const withQuery = <
TInput = inferProcedureInput<TQuery>,
TOutput = inferProcedureOutput<TQuery>
>(
queryProcedure: DecorateProcedure<TQuery, any, any>,
queryProcedure: DecorateProcedure<TQuery, inferProcedureInput<TQuery>, inferProcedureOutput<TQuery>>,
input?: TInput,
params?: UseTRPCQueryOptions<any, TInput, TOutput, TOutput, TError>
params?: UseTRPCQueryOptions<TQuery, TInput, TOutput, TOutput, TError>
) => {
return function WithQuery(
opts: Omit<
+10 -1
View File
@@ -9,6 +9,8 @@ import type { NextRouter } from "next/router";
import { useRouter } from "next/router";
import type { ComponentProps, PropsWithChildren, ReactNode } from "react";
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
import { FeatureProvider } from "@calcom/features/flags/context/provider";
@@ -205,6 +207,11 @@ function FeatureFlagsProvider({ children }: { children: React.ReactNode }) {
return <FeatureProvider value={flags}>{children}</FeatureProvider>;
}
function OrgBrandProvider({ children }: { children: React.ReactNode }) {
const orgBrand = useOrgBrandingValues();
return <OrgBrandingProvider value={orgBrand}>{children}</OrgBrandingProvider>;
}
const AppProviders = (props: AppPropsWithChildren) => {
const session = trpc.viewer.public.session.useQuery().data;
// No need to have intercom on public pages - Good for Page Performance
@@ -222,7 +229,9 @@ const AppProviders = (props: AppPropsWithChildren) => {
isThemeSupported={props.Component.isThemeSupported}
isBookingPage={props.Component.isBookingPage}>
<FeatureFlagsProvider>
<MetaProvider>{props.children}</MetaProvider>
<OrgBrandProvider>
<MetaProvider>{props.children}</MetaProvider>
</OrgBrandProvider>
</FeatureFlagsProvider>
</CalcomThemeProvider>
</TooltipProvider>
+19 -1
View File
@@ -5,7 +5,25 @@ export default function useRouterQuery<T extends string>(name: T) {
const existingQueryParams = router.asPath.split("?")[1];
const urlParams = new URLSearchParams(existingQueryParams);
const query = Object.fromEntries(urlParams);
const query: Record<string, string | string[]> = {};
// Following error is thrown by Typescript:
// 'Type 'URLSearchParams' can only be iterated through when using the '--downlevelIteration' flag or with a '--target' of 'es2015' or higher'
// We should change the target to higher ES2019 atleast maybe
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
for (const [key, value] of urlParams) {
if (!query[key]) {
query[key] = value;
} else {
let queryValue = query[key];
if (queryValue instanceof Array) {
queryValue.push(value);
} else {
queryValue = query[key] = [queryValue];
queryValue.push(value);
}
}
}
const setQuery = (newValue: string | number | null | undefined) => {
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
+2 -1
View File
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
type GetSSRResult<TProps> =
//
{ props: TProps } | { redirect: any } | { notFound: boolean };
{ props: TProps } | { redirect: { destination: string; permanent: boolean } } | { notFound: boolean };
type GetSSRFn<TProps> = (...args: any[]) => Promise<GetSSRResult<TProps>>;
+3
View File
@@ -8,6 +8,7 @@ export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
return async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<EmbedProps>> => {
const ssrResponse = await getServerSideProps(context);
const embed = context.query.embed;
const layout = context.query.layout;
if ("redirect" in ssrResponse) {
// Use a dummy URL https://base as the fallback base URL so that URL parsing works for relative URLs as well.
@@ -18,6 +19,8 @@ export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
destinationUrlObj.pathname +
"/embed?" +
destinationUrlObj.searchParams.toString() +
"&layout=" +
layout +
"&embed=" +
embed;
+41 -7
View File
@@ -3,6 +3,7 @@ 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";
@@ -10,6 +11,25 @@ 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";
return NextResponse.redirect(req.nextUrl);
}
if (!url.pathname.startsWith("/api")) {
//
@@ -48,13 +68,6 @@ const middleware: NextMiddleware = async (req) => {
}
}
// Ensure that embed query param is there in when /embed is added.
// query param is the way in which client side code knows that it is in embed mode.
if (url.pathname.endsWith("/embed") && typeof url.searchParams.get("embed") !== "string") {
url.searchParams.set("embed", "");
return NextResponse.redirect(url);
}
// Don't 404 old routing_forms links
if (url.pathname.startsWith("/apps/routing_forms")) {
url.pathname = url.pathname.replace("/apps/routing_forms", "/apps/routing-forms");
@@ -70,6 +83,26 @@ 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,
@@ -79,6 +112,7 @@ const middleware: NextMiddleware = async (req) => {
export const config = {
matcher: [
"/:path*",
"/api/collect-events/:path*",
"/api/auth/:path*",
"/apps/routing_forms/:path*",
+104 -12
View File
@@ -2,7 +2,7 @@ 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");
@@ -57,6 +57,20 @@ if (process.env.GOOGLE_API_CREDENTIALS && !validJson(process.env.GOOGLE_API_CRED
);
}
const informAboutDuplicateTranslations = () => {
const valueSet = new Set();
for (const key in englishTranslation) {
if (valueSet.has(englishTranslation[key])) {
console.warn("\x1b[33mDuplicate value found in:", "\x1b[0m", key);
} else {
valueSet.add(englishTranslation[key]);
}
}
};
informAboutDuplicateTranslations();
const plugins = [];
if (process.env.ANALYZE === "true") {
// only load dependency if env `ANALYZE` was set
@@ -169,7 +183,16 @@ const nextConfig = {
return config;
},
async rewrites() {
return [
// .* 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`;
let rewrites = [
{
source: "/org/:slug",
destination: "/team/:slug",
},
{
source: "/:user/avatar.png",
destination: "/api/user/avatar?username=:user",
@@ -201,21 +224,85 @@ const nextConfig = {
source: "/cancel/:path*",
destination: "/booking/:path*",
},
// Keep cookie based booker enabled just in case we disable new-booker globally
...[
{
source: userTypeRouteRegExp,
destination: "/new-booker/:user/:type",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/team/:slug/:type",
destination: "/new-booker/team/:slug/:type",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/d/:link/:slug",
destination: "/new-booker/d/:link/:slug",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
],
// Keep cookie based booker enabled to test new-booker embed in production
...[
{
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
destination: "/new-booker/:user/:type/embed",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/team/:slug/:type/embed",
destination: "/new-booker/team/:slug/:type/embed",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
],
/* TODO: have these files being served from another deployment or CDN {
source: "/embed/embed.js",
destination: process.env.NEXT_PUBLIC_EMBED_LIB_URL?,
}, */
{
source: `/:user((?!${pages.join("|")}).*)/:type`,
destination: "/new-booker/:user/:type",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
{
source: "/team/:slug/:type",
destination: "/new-booker/team/:slug/:type",
has: [{ type: "cookie", key: "new-booker-enabled" }],
},
/**
* Enables new booker using cookie. It works even if NEW_BOOKER_ENABLED_FOR_NON_EMBED, NEW_BOOKER_ENABLED_FOR_EMBED are disabled
*/
];
// Enable New Booker for all Embed Requests
if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") {
console.log("Enabling New Booker for Embed");
rewrites.push(
...[
{
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
destination: "/new-booker/:user/:type/embed",
},
{
source: "/team/:slug/:type/embed",
destination: "/new-booker/team/:slug/:type/embed",
},
]
);
}
// Enable New Booker for All but embed Requests
if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") {
console.log("Enabling New Booker for Non-Embed");
rewrites.push(
...[
{
source: userTypeRouteRegExp,
destination: "/new-booker/:user/:type",
},
{
source: "/team/:slug/:type",
destination: "/new-booker/team/:slug/:type",
},
{
source: "/d/:link/:slug",
destination: "/new-booker/d/:link/:slug",
},
]
);
}
return rewrites;
},
async headers() {
return [
@@ -274,6 +361,11 @@ const nextConfig = {
destination: "/teams",
permanent: true,
},
{
source: "/settings/admin",
destination: "/settings/admin/flags",
permanent: true,
},
/* V2 testers get redirected to the new settings */
{
source: "/settings/profile",
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.9.5",
"version": "3.0.2",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -61,6 +61,8 @@
"@tanstack/react-query": "^4.3.9",
"@tremor/react": "^2.0.0",
"@types/turndown": "^5.0.1",
"@upstash/ratelimit": "^0.4.3",
"@upstash/redis": "^1.21.0",
"@vercel/edge-config": "^0.1.1",
"@vercel/edge-functions-ui": "^0.2.1",
"@vercel/og": "^0.5.0",
+9 -1
View File
@@ -10,6 +10,7 @@ import {
useEmbedStyles,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
import { WEBAPP_URL } from "@calcom/lib/constants";
@@ -255,6 +256,7 @@ const getEventTypesWithHiddenFromDB = async (userId: number) => {
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
const crypto = await import("crypto");
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const usernameList = getUsernameList(context.query.user as string);
const dataFetchStart = Date.now();
@@ -263,6 +265,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
username: {
in: usernameList,
},
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: {
id: true,
@@ -272,6 +279,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
bio: true,
brandColor: true,
darkBrandColor: true,
organizationId: true,
theme: true,
away: true,
verified: true,
@@ -284,7 +292,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
avatar: `${WEBAPP_URL}/${user.username}/avatar.png`,
}));
if (!users.length) {
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
return {
notFound: true,
} as {
+56 -22
View File
@@ -1,8 +1,10 @@
import type { GetStaticPaths, GetStaticPropsContext } from "next";
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import type { LocationObject } from "@calcom/app-store/locations";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
@@ -15,7 +17,9 @@ import type { EmbedProps } from "@lib/withEmbedSsr";
import PageWrapper from "@components/PageWrapper";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps> & EmbedProps;
import { ssrInit } from "@server/lib/ssr";
export type AvailabilityPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export default function Type(props: AvailabilityPageProps) {
const { t } = useLocale();
@@ -50,6 +54,21 @@ export default function Type(props: AvailabilityPageProps) {
</div>
</main>
</div>
) : !props.isValidOrgDomain && props.organizationContext ? (
<div className="dark:bg-darkgray-50 h-screen">
<main className="mx-auto max-w-3xl px-4 py-24">
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="text-muted dark:text-inverted p-8 text-center">
<h2 className="font-cal dark:text-inverted text-emphasis600 mb-2 text-3xl">
{" " + t("unavailable")}
</h2>
<p className="mx-auto max-w-md">{t("user_belongs_organization")}</p>
</div>
</div>
</div>
</main>
</div>
) : (
<AvailabilityPage {...props} />
);
@@ -59,21 +78,25 @@ Type.isBookingPage = true;
Type.PageWrapper = PageWrapper;
const paramsSchema = z.object({ type: z.string(), user: z.string() });
async function getUserPageProps(context: GetStaticPropsContext) {
async function getUserPageProps(context: GetServerSidePropsContext) {
// load server side dependencies
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const { privacyFilteredLocations } = await import("@calcom/app-store/locations");
const { parseRecurringEvent } = await import("@calcom/lib/isRecurringEvent");
const { EventTypeMetaDataSchema, teamMetadataSchema } = await import("@calcom/prisma/zod-utils");
const { ssgInit } = await import("@server/lib/ssg");
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const ssr = await ssrInit(context);
const { type: slug, user: username } = paramsSchema.parse(context.query);
const { type: slug, user: username } = paramsSchema.parse(context.params);
const ssg = await ssgInit(context);
const user = await prisma.user.findUnique({
const user = await prisma.user.findFirst({
where: {
/** TODO: We should standarize this */
username: username.toLowerCase().replace(/( |%20)/g, "+"),
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: {
id: true,
@@ -87,6 +110,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
brandColor: true,
darkBrandColor: true,
metadata: true,
organizationId: true,
eventTypes: {
where: {
// Many-to-many relationship causes inclusion of the team events - cool -
@@ -108,6 +132,17 @@ async function getUserPageProps(context: GetStaticPropsContext) {
schedulingType: true,
metadata: true,
seatsPerTimeSlot: true,
team: {
select: {
logo: true,
parent: {
select: {
logo: true,
name: true,
},
},
},
},
},
orderBy: [
{
@@ -179,16 +214,17 @@ async function getUserPageProps(context: GetStaticPropsContext) {
},
// Dynamic group has no theme preference right now. It uses system theme.
themeBasis: user.username,
organizationContext: user?.organizationId !== null,
away: user?.away,
isDynamic: false,
trpcState: ssg.dehydrate(),
trpcState: ssr.dehydrate(),
isValidOrgDomain: orgDomainConfig(context.req.headers.host ?? ""),
isBrandingHidden: isBrandingHidden(user.hideBranding, hasActiveTeam || hasPremiumUserName),
},
revalidate: 10, // seconds
};
}
async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
// load server side dependencies
const { getDefaultEvent, getGroupName, getUsernameList } = await import("@calcom/lib/defaultEvents");
const { privacyFilteredLocations } = await import("@calcom/app-store/locations");
@@ -197,10 +233,10 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
const { EventTypeMetaDataSchema, userMetadata: userMetadataSchema } = await import(
"@calcom/prisma/zod-utils"
);
const { ssgInit } = await import("@server/lib/ssg");
const ssr = await ssrInit(context);
const { getAppFromSlug } = await import("@calcom/app-store/utils");
const ssg = await ssgInit(context);
const { type: typeParam, user: userParam } = paramsSchema.parse(context.params);
const usernameList = getUsernameList(userParam);
const length = parseInt(typeParam);
@@ -230,6 +266,7 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
defaultScheduleId: true,
allowDynamicBooking: true,
metadata: true,
organizationId: true,
away: true,
schedules: {
select: {
@@ -313,24 +350,21 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
themeBasis: null,
isDynamic: true,
away: false,
trpcState: ssg.dehydrate(),
organizationContext: !users.some((user) => user.organizationId === null),
trpcState: ssr.dehydrate(),
isValidOrgDomain: orgDomainConfig(context.req.headers.host ?? ""),
isBrandingHidden: false, // I think we should always show branding for dynamic groups - saves us checking every single user
},
revalidate: 10, // seconds
};
}
export const getStaticProps = async (context: GetStaticPropsContext) => {
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { user: userParam } = paramsSchema.parse(context.params);
// dynamic groups are not generated at build time, but otherwise are probably cached until infinity.
const isDynamicGroup = userParam.includes("+");
const isDynamicGroup = getUsernameList(userParam).length > 1;
if (isDynamicGroup) {
return await getDynamicGroupPageProps(context);
} else {
return await getUserPageProps(context);
}
};
export const getStaticPaths: GetStaticPaths = async () => {
return { paths: [], fallback: "blocking" };
};
}
+8 -10
View File
@@ -1,20 +1,18 @@
import type { GetStaticPropsContext } from "next";
import type { GetServerSidePropsContext } from "next";
import { getStaticProps as _getStaticProps } from "../[type]";
export { getStaticPaths } from "../[type]";
import { getServerSideProps as _getServerSideProps } from "../[type]";
export { default } from "../[type]";
export const getStaticProps = async (context: GetStaticPropsContext) => {
const staticResponse = await _getStaticProps(context);
if (staticResponse.notFound) {
return staticResponse;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssrResponse = await _getServerSideProps(context);
if (ssrResponse.notFound) {
return ssrResponse;
}
return {
...staticResponse,
...ssrResponse,
props: {
...staticResponse.props,
...ssrResponse.props,
isEmbed: true,
},
};
@@ -0,0 +1 @@
export { getStaticProps, getStaticPaths, default } from "./index";
@@ -3,22 +3,20 @@
* caching system that NextJS uses SSG pages.
* TODO: Redirect to user profile on browser
*/
import type { GetStaticPaths, GetStaticProps } from "next";
import type { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next";
import { z } from "zod";
import { getCachedResults } from "@calcom/core";
import getCalendarsEvents from "@calcom/core/getCalendarsEvents";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
const CalendarCache = () => <div />;
const paramsSchema = z.object({ user: z.string(), month: z.string() });
export const getStaticProps: GetStaticProps<
{ results: Awaited<ReturnType<typeof getCachedResults>> },
{ results: Awaited<ReturnType<typeof getCalendarsEvents>> },
{ user: string }
> = async (context) => {
const { user: username, month } = paramsSchema.parse(context.params);
const userWithCredentials = await prisma.user.findUnique({
const userWithCredentials = await prisma.user.findFirst({
where: {
username,
},
@@ -29,16 +27,16 @@ export const getStaticProps: GetStaticProps<
selectedCalendars: true,
},
});
const startDate = (
dayjs(month, "YYYY-MM").isSame(dayjs(), "month") ? dayjs.utc() : dayjs.utc(month, "YYYY-MM")
).startOf("day");
const endDate = startDate.endOf("month");
// 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 getCachedResults(
? await getCalendarsEvents(
userWithCredentials?.credentials,
startDate.format(),
endDate.format(),
startDate,
endDate,
userWithCredentials?.selectedCalendars
)
: [];
@@ -64,5 +62,8 @@ export const getStaticPaths: GetStaticPaths = () => {
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;
+9 -9
View File
@@ -5,15 +5,12 @@ 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 rateLimit from "@calcom/lib/rateLimit";
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
import rateLimiter from "@calcom/lib/rateLimit";
import { defaultHandler } from "@calcom/lib/server";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma from "@calcom/prisma";
const limiter = rateLimit({
intervalInMs: 60 * 1000, // 1 minute
});
async function handler(req: NextApiRequest, res: NextApiResponse) {
const t = await getTranslation(req.body.language ?? "en", "common");
@@ -37,10 +34,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// 10 requests per minute
try {
limiter.check(10, ip);
} catch (e) {
return res.status(429).json({ message: "Too Many Requests." });
const limiter = await rateLimiter();
const limit = await limiter({
identifier: ip,
});
if (!limit.success) {
throw new Error(ErrorCode.RateLimitExceeded);
}
try {
+17 -11
View File
@@ -1,11 +1,20 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
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";
const signupSchema = z.object({
username: z.string(),
email: z.string().email(),
password: z.string().min(7),
language: z.string().optional(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return;
@@ -17,7 +26,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
const data = req.body;
const { email, password } = data;
const { email, password, language } = signupSchema.parse(data);
const username = slugify(data.username);
const userEmail = email.toLowerCase();
@@ -26,16 +36,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return;
}
if (!userEmail || !userEmail.includes("@")) {
res.status(422).json({ message: "Invalid email" });
return;
}
if (!password || password.trim().length < 7) {
res.status(422).json({ message: "Invalid input - password should be at least 7 characters long." });
return;
}
// There is an existingUser if the username matches
// OR if the email matches AND either the email is verified
// or both username and password are set
@@ -106,5 +106,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
await sendEmailVerification({
email: userEmail,
username,
language,
});
res.status(201).json({ message: "Created user" });
}
+48
View File
@@ -0,0 +1,48 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import dayjs from "@calcom/dayjs";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { prisma } from "@calcom/prisma";
const verifySchema = z.object({
token: z.string(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { token } = verifySchema.parse(req.query);
const foundToken = await prisma.verificationToken.findFirst({
where: {
token,
},
});
if (!foundToken) {
return res.status(401).json({ message: "No token found" });
}
if (dayjs(foundToken?.expires).isBefore(dayjs())) {
return res.status(401).json({ message: "Token expired" });
}
const user = await prisma.user.update({
where: {
email: foundToken?.identifier,
},
data: {
emailVerified: new Date(),
},
});
// Delete token from DB after it has been used
await prisma.verificationToken.delete({
where: {
id: foundToken?.id,
},
});
const hasCompletedOnboarding = user.completedOnboarding;
res.redirect(`${WEBAPP_URL}/${hasCompletedOnboarding ? "/event-types" : "/getting-started"}`);
}
+8 -3
View File
@@ -60,9 +60,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
renderEmail("OrganizerRequestEmail", {
calEvent: evt,
attendee: evt.organizer,
renderEmail("VerifyAccountEmail", {
language: t,
user: {
name: "Pro Example",
email: "pro@example.com",
},
verificationEmailLink:
"http://localhost:3000/api/auth/verify-email?token=b91af0eee5a9a24a8d83a3d3d6a58c1606496e94ced589441649273c66100f5b",
})
);
res.end();
+27 -4
View File
@@ -4,6 +4,7 @@ import { z } from "zod";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { TRPCError } from "@calcom/trpc/server";
import { createContext } from "@calcom/trpc/server/createContext";
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
@@ -26,7 +27,7 @@ const decryptedSchema = z.object({
async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
const { action, token, reason } = querySchema.parse(req.query);
const { bookingUid } = decryptedSchema.parse(
const { bookingUid, userId } = decryptedSchema.parse(
JSON.parse(symmetricDecrypt(decodeURIComponent(token), process.env.CALENDSO_ENCRYPTION_KEY || ""))
);
@@ -34,10 +35,32 @@ async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
where: { uid: bookingUid },
});
const user = await prisma.user.findUniqueOrThrow({
where: { id: userId },
});
/** We shape the session as required by tRPC router */
async function sessionGetter() {
return {
user: {
id: userId,
username: "" /* Not used in this context */,
role: UserPermissionRole.USER,
},
hasValidLicense: true,
expires: "" /* Not used in this context */,
};
}
try {
/** @see https://trpc.io/docs/server-side-calls */
const ctx = await createContext({ req, res });
const caller = viewerRouter.createCaller({ ...ctx, req, res });
const ctx = await createContext({ req, res }, sessionGetter);
const caller = viewerRouter.createCaller({
...ctx,
req,
res,
user: { ...user, locale: user?.locale ?? "en" },
});
await caller.bookings.confirm({
bookingId: booking.id,
recurringEventId: booking.recurringEventId || undefined,
@@ -46,7 +69,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
});
} catch (e) {
let message = "Error confirming booking";
if (e instanceof TRPCError) message = e.message;
if (e instanceof TRPCError) message = (e as TRPCError).message;
res.redirect(`/booking/${bookingUid}?error=${encodeURIComponent(message)}`);
return;
}
+11 -3
View File
@@ -1,6 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import {
ANDROID_CHROME_ICON_192,
ANDROID_CHROME_ICON_256,
@@ -104,7 +105,7 @@ function isValidLogoType(type: string): type is LogoType {
return type in logoDefinitions;
}
async function getTeamLogos(subdomain: string) {
async function getTeamLogos(subdomain: string, isValidOrgDomain: boolean) {
try {
if (
// if not cal.com
@@ -118,9 +119,15 @@ async function getTeamLogos(subdomain: string) {
}
// load from DB
const { default: prisma } = await import("@calcom/prisma");
const team = await prisma.team.findUnique({
const team = await prisma.team.findFirst({
where: {
slug: subdomain,
...(isValidOrgDomain && {
metadata: {
path: ["isOrganization"],
equals: true,
},
}),
},
select: {
appLogo: true,
@@ -147,6 +154,7 @@ async function getTeamLogos(subdomain: string) {
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { query } = req;
const parsedQuery = logoApiSchema.parse(query);
const { isValidOrgDomain } = orgDomainConfig(req.headers.host ?? "");
const hostname = req?.headers["host"];
if (!hostname) throw new Error("No hostname");
@@ -154,7 +162,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (!domains) throw new Error("No domains");
const [subdomain] = domains;
const teamLogos = await getTeamLogos(subdomain);
const teamLogos = await getTeamLogos(subdomain, isValidOrgDomain);
// Resolve all icon types to team logos, falling back to Cal.com defaults.
const type: LogoType = parsedQuery?.type && isValidLogoType(parsedQuery.type) ? parsedQuery.type : "logo";
@@ -0,0 +1 @@
export { default } from "@calcom/features/ee/organizations/api/subteams";
-4
View File
@@ -1,4 +0,0 @@
import ethRouter from "@calcom/app-store/rainbow/trpc/router";
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
export default createNextApiHandler(ethRouter);
@@ -0,0 +1,4 @@
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
import { viewerOrganizationsRouter } from "@calcom/trpc/server/routers/viewer/organizations/_router";
export default createNextApiHandler(viewerOrganizationsRouter);
+20 -4
View File
@@ -2,6 +2,7 @@ import crypto from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import prisma from "@calcom/prisma";
@@ -16,10 +17,18 @@ const querySchema = z
async function getIdentityData(req: NextApiRequest) {
const { username, teamname } = querySchema.parse(req.query);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.host ?? "");
if (username) {
const user = await prisma.user.findUnique({
where: { username },
const user = await prisma.user.findFirst({
where: {
username,
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: { avatar: true, email: true },
});
return {
@@ -29,8 +38,15 @@ async function getIdentityData(req: NextApiRequest) {
};
}
if (teamname) {
const team = await prisma.team.findUnique({
where: { slug: teamname },
const team = await prisma.team.findFirst({
where: {
slug: teamname,
parent: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: { logo: true },
});
return {
+3 -1
View File
@@ -1,5 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { checkUsername } from "@calcom/lib/server/checkUsername";
type Response = {
@@ -8,6 +9,7 @@ type Response = {
};
export default async function handler(req: NextApiRequest, res: NextApiResponse<Response>): Promise<void> {
const result = await checkUsername(req.body.username);
const { currentOrgDomain } = orgDomainConfig(req.headers.host ?? "");
const result = await checkUsername(req.body.username, currentOrgDomain);
return res.status(200).json(result);
}
+1 -1
View File
@@ -182,7 +182,7 @@ const IntegrationsList = ({ data, handleDisconnect, variant }: IntegrationsListP
setBulkUpdateModal(true);
}
}}>
{t("change_default_conferencing_app")}
{t("set_as_default")}
</DropdownItem>
</DropdownMenuItem>
)}
+1
View File
@@ -19,6 +19,7 @@ export default function Page() {
callbackUrl: "/",
code,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
+2 -1
View File
@@ -5,6 +5,7 @@ import { useState } from "react";
import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { UserPermissionRole } from "@calcom/prisma/enums";
@@ -95,7 +96,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
steps.push({
title: t("enable_apps"),
description: t("enable_apps_description"),
description: t("enable_apps_description", { appName: APP_NAME }),
contentClassname: "!pb-0 mb-[-1px]",
content: (setIsLoading) => {
const currentStep = isFreeLicense ? 3 : 4;
+7 -3
View File
@@ -5,6 +5,7 @@ import { useEffect } from "react";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import stripe from "@calcom/features/ee/payments/server/stripe";
import {
hostedCal,
@@ -68,11 +69,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const session = await getServerSession({ req, res });
const ssr = await ssrInit(context);
const { currentOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
if (session) {
// Validating if username is Premium, while this is true an email its required for stripe user confirmation
if (usernameParam && session.user.email) {
const availability = await checkUsername(usernameParam);
const availability = await checkUsername(usernameParam, currentOrgDomain);
if (availability.available && availability.premium) {
const stripePremiumUrl = await getStripePremiumUsernameUrl({
userEmail: session.user.email,
@@ -111,8 +113,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const ret = await samlTenantProduct(prisma, emailParam);
tenant = ret.tenant;
product = ret.product;
} catch (e: any) {
error = e.message;
} catch (e) {
if (e instanceof Error) {
error = e.message;
}
}
}
}
+2
View File
@@ -18,6 +18,7 @@ export default function Page({ samlTenantID, samlProductID }: inferSSRProps<type
if (HOSTED_CAL_FEATURES) {
router.push("/auth/login");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
@@ -29,6 +30,7 @@ export default function Page({ samlTenantID, samlProductID }: inferSSRProps<type
},
{ tenant: samlTenantID, product: samlProductID }
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
+60
View File
@@ -0,0 +1,60 @@
import { MailOpenIcon } from "lucide-react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import useEmailVerifyCheck from "@calcom/trpc/react/hooks/useEmailVerifyCheck";
import { Button, EmptyScreen, showToast } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
function VerifyEmailPage() {
const { data } = useEmailVerifyCheck();
const { data: session } = useSession();
const router = useRouter();
const { t } = useLocale();
const mutation = trpc.viewer.auth.resendVerifyEmail.useMutation();
useEffect(() => {
if (data?.isVerified) {
router.replace("/getting-started");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data?.isVerified]);
return (
<div className="h-[100vh] w-full ">
<div className="flex h-full w-full flex-col items-center justify-center">
<div className="max-w-3xl">
<EmptyScreen
border
dashedBorder={false}
Icon={MailOpenIcon}
headline={t("check_your_email")}
description={t("verify_email_page_body", { email: session?.user?.email, appName: APP_NAME })}
className="bg-default"
buttonRaw={
<Button
color="minimal"
className="underline"
loading={mutation.isLoading}
onClick={() => {
showToast("Send email", "success");
mutation.mutate();
}}>
Resend Email
</Button>
}
/>
</div>
</div>
</div>
);
}
export default VerifyEmailPage;
VerifyEmailPage.PageWrapper = PageWrapper;
+2 -1
View File
@@ -105,7 +105,8 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
);
}
const WithQuery = withQuery(trpc.viewer.availability.list);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const WithQuery = withQuery(trpc.viewer.availability.list as any);
export default function AvailabilityPage() {
const { t } = useLocale();
+1 -1
View File
@@ -26,7 +26,7 @@ const AvailabilityView = ({ user }: { user: User }) => {
const { data, isLoading } = trpc.viewer.availability.user.useQuery(
{
username: user.username!,
username: user.username || "",
dateFrom: selectedDate.startOf("day").utc().format(),
dateTo: selectedDate.endOf("day").utc().format(),
withSource: true,
+4 -6
View File
@@ -102,7 +102,6 @@ export default function Success(props: SuccessProps) {
allRemainingBookings,
isSuccessBookingPage,
cancel: isCancellationMode,
changes,
formerTime,
email,
seatReferenceUid,
@@ -222,6 +221,7 @@ export default function Success(props: SuccessProps) {
setCalculatedDuration(
dayjs(props.bookingInfo.endTime).diff(dayjs(props.bookingInfo.startTime), "minutes")
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function eventLink(): string {
@@ -279,9 +279,7 @@ export default function Success(props: SuccessProps) {
darkBrandColor: props.profile.darkBrandColor,
});
const title = t(
`booking_${needsConfirmation ? "booking_submitted" : "confirmed"}${
props.recurringBookings ? "_recurring" : ""
}`
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
);
const locationToDisplay = getSuccessPageLocationMessage(
@@ -365,7 +363,7 @@ export default function Success(props: SuccessProps) {
id="modal-headline">
{needsConfirmation && !isCancelled
? props.recurringBookings
? t("submitted_recurring")
? t("booking_submitted_recurring")
: t("booking_submitted")
: isCancelled
? seatReferenceUid
@@ -402,7 +400,7 @@ export default function Success(props: SuccessProps) {
</>
)}
<div className="font-medium">{t("what")}</div>
<div className="col-span-2 mb-6 last:mb-0">{props.bookingInfo.title}</div>
<div className="col-span-2 mb-6 last:mb-0">{eventName}</div>
<div className="font-medium">{t("when")}</div>
<div className="col-span-2 mb-6 last:mb-0">
{reschedule && !!formerTime && (
+2 -2
View File
@@ -22,7 +22,7 @@ import SkeletonLoader from "@components/booking/SkeletonLoader";
import { ssgInit } from "@server/lib/ssg";
type BookingListingStatus = z.infer<typeof filterQuerySchema>["status"];
type BookingListingStatus = z.infer<NonNullable<typeof filterQuerySchema>>["status"];
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
type RecurringInfo = {
@@ -34,7 +34,7 @@ type RecurringInfo = {
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
const descriptionByStatus: Record<BookingListingStatus, string> = {
const descriptionByStatus: Record<NonNullable<BookingListingStatus>, string> = {
upcoming: "upcoming_bookings",
recurring: "recurring_bookings",
past: "past_bookings",
+60 -46
View File
@@ -3,7 +3,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 { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -17,9 +17,14 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { validateBookerLayouts } from "@calcom/lib/validateBookerLayouts";
import type { Prisma } from "@calcom/prisma/client";
import type { PeriodType, SchedulingType } from "@calcom/prisma/enums";
import type { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type {
BookerLayoutSettings,
customInputSchema,
EventTypeMetaDataSchema,
} from "@calcom/prisma/zod-utils";
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
@@ -96,6 +101,7 @@ export type FormValues = {
hosts: { userId: number; isFixed: boolean }[];
bookingFields: z.infer<typeof eventTypeBookingFields>;
availability?: AvailabilityOption;
bookerLayouts: BookerLayoutSettings;
};
export type CustomInputParsed = typeof customInputSchema._output;
@@ -184,17 +190,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
const metadata = eventType.metadata;
// fallback to !!eventType.schedule when 'useHostSchedulesForTeamEvent' is undefined
if (!!team) {
if (!!team && metadata !== null) {
metadata.config = {
...metadata.config,
useHostSchedulesForTeamEvent:
typeof eventType.metadata.config?.useHostSchedulesForTeamEvent !== "undefined"
? eventType.metadata.config?.useHostSchedulesForTeamEvent === true
typeof eventType.metadata?.config?.useHostSchedulesForTeamEvent !== "undefined"
? eventType.metadata?.config?.useHostSchedulesForTeamEvent === true
: !!eventType.schedule,
};
} else {
// Make sure non-team events NEVER have this config key;
delete metadata.config?.useHostSchedulesForTeamEvent;
delete metadata?.config?.useHostSchedulesForTeamEvent;
}
const bookingFields: Prisma.JsonObject = {};
@@ -203,41 +209,43 @@ const EventTypePage = (props: EventTypeSetupProps) => {
bookingFields[name] = name;
});
const defaultValues = {
title: eventType.title,
locations: eventType.locations || [],
recurringEvent: eventType.recurringEvent || null,
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
bookingLimits: eventType.bookingLimits || undefined,
durationLimits: eventType.durationLimits || undefined,
length: eventType.length,
offsetStart: eventType.offsetStart,
hidden: eventType.hidden,
periodDates: {
startDate: periodDates.startDate,
endDate: periodDates.endDate,
},
bookingFields: eventType.bookingFields,
periodType: eventType.periodType,
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
schedulingType: eventType.schedulingType,
minimumBookingNotice: eventType.minimumBookingNotice,
metadata,
hosts: eventType.hosts,
children: eventType.children.map((ch) => ({
...ch,
created: true,
owner: {
...ch.owner,
eventTypeSlugs:
eventType.team?.members
.find((mem) => mem.user.id === ch.owner.id)
?.user.eventTypes.map((evTy) => evTy.slug)
.filter((slug) => slug !== eventType.slug) ?? [],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const defaultValues: any = useMemo(() => {
return {
title: eventType.title,
locations: eventType.locations || [],
recurringEvent: eventType.recurringEvent || null,
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
bookingLimits: eventType.bookingLimits || undefined,
durationLimits: eventType.durationLimits || undefined,
length: eventType.length,
hidden: eventType.hidden,
periodDates: {
startDate: periodDates.startDate,
endDate: periodDates.endDate,
},
})),
} as const;
bookingFields: eventType.bookingFields,
periodType: eventType.periodType,
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
schedulingType: eventType.schedulingType,
minimumBookingNotice: eventType.minimumBookingNotice,
metadata,
hosts: eventType.hosts,
children: eventType.children.map((ch) => ({
...ch,
created: true,
owner: {
...ch.owner,
eventTypeSlugs:
eventType.team?.members
.find((mem) => mem.user.id === ch.owner.id)
?.user.eventTypes.map((evTy) => evTy.slug)
.filter((slug) => slug !== eventType.slug) ?? [],
},
})),
};
}, [eventType, periodDates, metadata]);
const formMethods = useForm<FormValues>({
defaultValues,
@@ -270,6 +278,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
//TODO: What's the best way to sync the form with backend
formMethods.setValue("bookingFields", defaultValues.bookingFields);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultValues]);
const appsMetadata = formMethods.getValues("metadata")?.apps;
@@ -332,6 +341,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
seatsPerTimeSlotEnabled,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
minimumBookingNoticeInDurationType,
bookerLayouts,
...input
} = values;
@@ -345,6 +355,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
if (!isValid) throw new Error(t("event_setup_duration_limits_error"));
}
const layoutError = validateBookerLayouts(metadata?.bookerLayouts || null);
if (layoutError) throw new Error(t(layoutError));
if (metadata?.multipleDuration !== undefined) {
if (metadata?.multipleDuration.length < 1) {
throw new Error(t("event_setup_multiple_duration_error"));
@@ -359,8 +372,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
throw new Error(t("seats_and_no_show_fee_error"));
}
const { availability, ...rest } = input;
updateMutation.mutate({
...input,
...rest,
locations,
recurringEvent,
periodStartDate: periodDates.startDate,
@@ -415,9 +429,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
// We don't need to send send these values to the backend
// eslint-disable-next-line @typescript-eslint/no-unused-vars
seatsPerTimeSlotEnabled,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
minimumBookingNoticeInDurationType,
availability,
...input
} = values;
@@ -431,6 +442,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
if (!isValid) throw new Error(t("event_setup_duration_limits_error"));
}
const layoutError = validateBookerLayouts(metadata?.bookerLayouts || null);
if (layoutError) throw new Error(t(layoutError));
if (metadata?.multipleDuration !== undefined) {
if (metadata?.multipleDuration.length < 1) {
throw new Error(t("event_setup_multiple_duration_error"));
@@ -440,9 +454,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
}
}
}
const { availability, ...rest } = input;
updateMutation.mutate({
...input,
...rest,
locations,
recurringEvent,
periodStartDate: periodDates.startDate,
+80 -9
View File
@@ -7,10 +7,13 @@ import type { FC } from "react";
import { useEffect, useState, memo } from "react";
import { z } from "zod";
import { useOrgBrandingValues } from "@calcom/features/ee/organizations/hooks";
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
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 Shell from "@calcom/features/shell/Shell";
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -44,6 +47,8 @@ import {
HeadSeo,
Skeleton,
Label,
VerticalDivider,
Alert,
} from "@calcom/ui";
import {
ArrowDown,
@@ -59,9 +64,11 @@ import {
Trash,
Upload,
Users,
User as UserIcon,
} from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
import useMeQuery from "@lib/hooks/useMeQuery";
import { EmbedButton, EmbedDialog } from "@components/Embed";
import PageWrapper from "@components/PageWrapper";
@@ -74,6 +81,7 @@ interface EventTypeListHeadingProps {
profile: EventTypeGroupProfile;
membershipCount: number;
teamId?: number | null;
orgSlug?: string;
}
type EventTypeGroup = EventTypeGroups[number];
@@ -194,6 +202,7 @@ const MemoizedItem = memo(Item);
export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeListProps): JSX.Element => {
const { t } = useLocale();
const router = useRouter();
const orgBranding = useOrgBrandingValues();
const [parent] = useAutoAnimate<HTMLUListElement>();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [deleteDialogTypeId, setDeleteDialogTypeId] = useState(0);
@@ -362,7 +371,9 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<ul ref={parent} className="divide-subtle !static w-full divide-y" data-testid="event-types">
{types.map((type, index) => {
const embedLink = `${group.profile.slug}/${type.slug}`;
const calLink = `${CAL_URL}/${embedLink}`;
const calLink = `${
orgBranding ? `${new URL(CAL_URL).protocol}//${orgBranding.slug}.${subdomainSuffix()}` : CAL_URL
}/${embedLink}`;
const isManagedEventType = type.schedulingType === SchedulingType.MANAGED;
const isChildrenManagedEventType =
type.metadata?.managedEventConfig !== undefined && type.schedulingType !== SchedulingType.MANAGED;
@@ -393,11 +404,13 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
className="relative top-1 right-3"
size="sm"
truncateAfter={4}
items={type.users.map((organizer: { name: any; username: any }) => ({
alt: organizer.name || "",
image: `${WEBAPP_URL}/${organizer.username}/avatar.png`,
title: organizer.name || "",
}))}
items={type.users.map(
(organizer: { name: string | null; username: string | null }) => ({
alt: organizer.name || "",
image: `${WEBAPP_URL}/${organizer.username}/avatar.png`,
title: organizer.name || "",
})
)}
/>
)}
{isManagedEventType && (
@@ -685,6 +698,7 @@ const EventTypeListHeading = ({
profile,
membershipCount,
teamId,
orgSlug,
}: EventTypeListHeadingProps): JSX.Element => {
const { t } = useLocale();
const router = useRouter();
@@ -725,7 +739,9 @@ const EventTypeListHeading = ({
)}
{profile?.slug && (
<Link href={`${CAL_URL}/${profile.slug}`} className="text-subtle block text-xs">
{`${CAL_URL?.replace("https://", "")}/${profile.slug}`}
{orgSlug
? `${orgSlug}.${subdomainSuffix()}/${profile.slug}`
: `${CAL_URL?.replace("https://", "")}/${profile.slug}`}
</Link>
)}
</div>
@@ -773,6 +789,7 @@ const CTA = () => {
return (
<CreateButton
data-testid="new-event-type"
subtitle={t("create_event_on").toUpperCase()}
options={profileOptions}
createDialog={() => <CreateEventTypeDialog profileOptions={profileOptions} />}
@@ -780,19 +797,70 @@ const CTA = () => {
);
};
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer);
const Actions = () => {
return (
<div className="hidden items-center md:flex">
<OrganizationEventTypeFilter />
<VerticalDivider />
</div>
);
};
const SetupProfileBanner = ({ closeAction }: { closeAction: () => void }) => {
const { t } = useLocale();
const orgBranding = useOrgBrandingValues();
return (
<Alert
className="my-4"
severity="info"
title={t("set_up_your_profile")}
message={t("set_up_your_profile_description", { orgName: orgBranding?.name })}
CustomIcon={UserIcon}
actions={
<div className="flex gap-1">
<Button color="minimal" className="text-sky-700 hover:bg-sky-100" onClick={closeAction}>
{t("dismiss")}
</Button>
<Button
color="secondary"
className="border-sky-700 bg-sky-50 text-sky-700 hover:border-sky-900 hover:bg-sky-200"
href="/getting-started">
{t("set_up")}
</Button>
</div>
}
/>
);
};
// 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();
const { open } = useIntercom();
const { query } = router;
const { data: user } = useMeQuery();
const isMobile = useMediaQuery("(max-width: 768px)");
const [showProfileBanner, setShowProfileBanner] = useState(false);
const orgBranding = useOrgBrandingValues();
function closeBanner() {
setShowProfileBanner(false);
document.cookie = `calcom-profile-banner=1;max-age=${60 * 60 * 24 * 90}`; // 3 months
showToast(t("we_wont_show_again"), "success");
}
useEffect(() => {
if (query?.openIntercom && query?.openIntercom === "true") {
open();
}
setShowProfileBanner(
!!orgBranding && !document.cookie.includes("calcom-profile-banner=1") && !user?.completedOnboarding
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
@@ -806,6 +874,8 @@ const EventTypesPage = () => {
heading={t("event_types_page_title")}
hideHeadingOnMobile
subtitle={t("event_types_page_subtitle")}
afterHeading={showProfileBanner && <SetupProfileBanner closeAction={closeBanner} />}
beforeCTAactions={<Actions />}
CTA={<CTA />}>
<WithQuery
customLoader={<SkeletonLoader />}
@@ -816,12 +886,13 @@ const EventTypesPage = () => {
{isMobile ? (
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
) : (
data.eventTypeGroups.map((group, index) => (
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}
/>
<EventTypeList
+24 -38
View File
@@ -3,15 +3,16 @@ import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Head from "next/head";
import { useRouter } from "next/router";
import type { CSSProperties } from "react";
import { Suspense } from "react";
import { z } from "zod";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { trpc } from "@calcom/trpc";
import { Button, StepCard, Steps } from "@calcom/ui";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import { Loader } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars";
@@ -20,7 +21,7 @@ import { SetupAvailability } from "@components/getting-started/steps-views/Setup
import UserProfile from "@components/getting-started/steps-views/UserProfile";
import { UserSettings } from "@components/getting-started/steps-views/UserSettings";
export type IOnboardingPageProps = inferSSRProps<typeof getServerSideProps>;
import { ssrInit } from "@server/lib/ssr";
const INITIAL_STEP = "user-settings";
const steps = [
@@ -44,9 +45,9 @@ const stepRouteSchema = z.object({
});
// TODO: Refactor how steps work to be contained in one array/object. Currently we have steps,initalsteps,headers etc. These can all be in one place
const OnboardingPage = (props: IOnboardingPageProps) => {
const OnboardingPage = () => {
const router = useRouter();
const { user } = props;
const [user] = trpc.viewer.me.useSuspenseQuery();
const { t } = useLocale();
const result = stepRouteSchema.safeParse(router.query);
@@ -117,9 +118,7 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
}
key={router.asPath}>
<Head>
<title>
{APP_NAME} - {t("getting_started")}
</title>
<title>{`${APP_NAME} - ${t("getting_started")}`}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
@@ -141,17 +140,20 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
<Steps maxSteps={steps.length} currentStep={currentStepIndex + 1} navigateToStep={goToIndex} />
</div>
<StepCard>
{currentStep === "user-settings" && <UserSettings user={user} nextStep={() => goToIndex(1)} />}
<Suspense fallback={<Loader />}>
{currentStep === "user-settings" && <UserSettings nextStep={() => goToIndex(1)} />}
{currentStep === "connected-calendar" && <ConnectedCalendars nextStep={() => goToIndex(2)} />}
{currentStep === "connected-calendar" && <ConnectedCalendars nextStep={() => goToIndex(2)} />}
{currentStep === "connected-video" && <ConnectedVideoStep nextStep={() => goToIndex(3)} />}
{currentStep === "connected-video" && <ConnectedVideoStep nextStep={() => goToIndex(3)} />}
{currentStep === "setup-availability" && (
<SetupAvailability nextStep={() => goToIndex(4)} defaultScheduleId={user.defaultScheduleId} />
)}
{currentStep === "user-profile" && <UserProfile user={user} />}
{currentStep === "setup-availability" && (
<SetupAvailability
nextStep={() => goToIndex(4)}
defaultScheduleId={user.defaultScheduleId}
/>
)}
{currentStep === "user-profile" && <UserProfile />}
</Suspense>
</StepCard>
{headers[currentStepIndex]?.skipText && (
@@ -178,34 +180,21 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, res } = context;
const crypto = await import("crypto");
const session = await getServerSession({ req, res });
if (!session?.user?.id) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
const ssr = await ssrInit(context);
await ssr.viewer.me.prefetch();
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
select: {
id: true,
username: true,
name: true,
email: true,
bio: true,
avatar: true,
timeZone: true,
weekStart: true,
hideBranding: true,
theme: true,
brandColor: true,
darkBrandColor: true,
metadata: true,
timeFormat: true,
allowDynamicBooking: true,
defaultScheduleId: true,
completedOnboarding: true,
teams: {
select: {
@@ -233,10 +222,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
...(await serverSideTranslations(context.locale ?? "", ["common"])),
user: {
...user,
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
},
trpcState: ssr.dehydrate(),
hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false,
},
};
+1 -1
View File
@@ -25,7 +25,7 @@ const Heading = () => {
return (
<div className="min-w-52 hidden md:block">
<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("analytics_for_organisation")}
{t("insights")}
</h3>
<p className="text-default hidden text-sm md:block">{t("subtitle_analytics")}</p>
</div>
+14 -3
View File
@@ -5,6 +5,8 @@ import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { classNames } from "@calcom/lib";
import { getUsernameList } from "@calcom/lib/defaultEvents";
import prisma from "@calcom/prisma";
@@ -15,8 +17,9 @@ import PageWrapper from "@components/PageWrapper";
type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
return (
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
<BookerSeo
username={user}
eventSlug={slug}
@@ -84,6 +87,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
away: false,
trpcState: ssr.dehydrate(),
isBrandingHidden: false,
themeBasis: null,
},
};
}
@@ -91,12 +95,18 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
async function getUserPageProps(context: GetServerSidePropsContext) {
const { user: username, type: slug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const user = await prisma.user.findUnique({
const user = await prisma.user.findFirst({
where: {
username,
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: {
away: true,
@@ -133,6 +143,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
slug,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
themeBasis: username,
},
};
}
@@ -143,7 +154,7 @@ const paramsSchema = z.object({ type: z.string(), user: z.string() });
// whether the page should show an away state or dynamic booking not allowed.
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { user } = paramsSchema.parse(context.params);
const isDynamicGroup = user.includes("+");
const isDynamicGroup = getUsernameList(user).length > 1;
return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context);
};
@@ -0,0 +1,11 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[type]";
export { default } from "../[type]";
// Somehow these types don't accept the {notFound: true} return type.
// Probably still need to fix this. I don't know why this isn't allowed yet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
@@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../../[user]";
export { default } from "../../[user]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
@@ -0,0 +1,127 @@
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import prisma from "@calcom/prisma";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
return (
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<BookerSeo
username={user}
eventSlug={slug}
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
/>
<Booker
username={user}
eventSlug={slug}
rescheduleBooking={booking}
isAway={away}
hideBranding={isBrandingHidden}
/>
</main>
);
}
Type.PageWrapper = PageWrapper;
async function getUserPageProps(context: GetServerSidePropsContext) {
const { link, slug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const hashedLink = await prisma.hashedLink.findUnique({
where: {
link,
},
select: {
eventTypeId: true,
eventType: {
select: {
users: {
select: {
username: true,
},
},
},
},
},
});
const username = hashedLink?.eventType.users[0]?.username;
if (!hashedLink || !username) {
return {
notFound: true,
};
}
const user = await prisma.user.findFirst({
where: {
username,
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: {
away: true,
hideBranding: true,
},
});
if (!user) {
return {
notFound: true,
};
}
let booking: GetBookingType | null = null;
if (rescheduleUid) {
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
}
// 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 });
if (!eventData) {
return {
notFound: true,
};
}
return {
props: {
booking,
away: user?.away,
user: username,
slug,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
},
};
}
const paramsSchema = z.object({ link: z.string(), slug: z.string() });
// Booker page fetches a tiny bit of data server side, to determine early
// whether the page should show an away state or dynamic booking not allowed.
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
return await getUserPageProps(context);
};
@@ -5,6 +5,7 @@ import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { classNames } from "@calcom/lib";
import prisma from "@calcom/prisma";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -14,8 +15,9 @@ import PageWrapper from "@components/PageWrapper";
type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
return (
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
<BookerSeo
username={user}
eventSlug={slug}
@@ -85,6 +87,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
slug: meetingSlug,
trpcState: ssr.dehydrate(),
isBrandingHidden: team?.hideBranding,
themeBasis: null,
},
};
};
@@ -0,0 +1,9 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[type]";
export { default } from "../[type]";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
@@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../../../team/[slug]";
export { default } from "../../../team/[slug]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
+2 -4
View File
@@ -47,15 +47,13 @@ const BillingView = () => {
<>
<Meta title={t("billing")} description={t("manage_billing_description")} />
<div className="space-y-6 text-sm sm:space-y-8">
<CtaRow
title={t("billing_manage_details_title")}
description={t("billing_manage_details_description")}>
<CtaRow title={t("view_and_manage_billing_details")} description={t("view_and_edit_billing_details")}>
<Button color="primary" href={billingHref} target="_blank" EndIcon={ExternalLink}>
{t("billing_portal")}
</Button>
</CtaRow>
<CtaRow title={t("billing_help_title")} description={t("billing_help_description")}>
<CtaRow title={t("need_anything_else")} description={t("further_billing_help")}>
<Button color="secondary" onClick={onContactSupportClick}>
{t("contact_support")}
</Button>
@@ -1,12 +1,16 @@
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import type { z } from "zod";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import ThemeLabel from "@calcom/features/settings/ThemeLabel";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { APP_NAME } from "@calcom/lib/constants";
import { checkWCAGContrastColor } from "@calcom/lib/getBrandColours";
import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { validateBookerLayouts } from "@calcom/lib/validateBookerLayouts";
import type { userMetadata } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import {
Alert,
@@ -62,6 +66,7 @@ const AppearanceView = () => {
brandColor: user?.brandColor || "#292929",
darkBrandColor: user?.darkBrandColor || "#fafafa",
hideBranding: user?.hideBranding,
metadata: user?.metadata as z.infer<typeof userMetadata>,
},
});
@@ -76,8 +81,12 @@ const AppearanceView = () => {
showToast(t("settings_updated_successfully"), "success");
reset(data);
},
onError: () => {
showToast(t("error_updating_settings"), "error");
onError: (error) => {
if (error.message) {
showToast(error.message, "error");
} else {
showToast(t("error_updating_settings"), "error");
}
},
});
@@ -92,6 +101,9 @@ const AppearanceView = () => {
<Form
form={formMethods}
handleSubmit={(values) => {
const layoutError = validateBookerLayouts(values?.metadata?.defaultBookerLayouts || null);
if (layoutError) throw new Error(t(layoutError));
mutation.mutate({
...values,
// Radio values don't support null as values, therefore we convert an empty string
@@ -117,19 +129,26 @@ const AppearanceView = () => {
<ThemeLabel
variant="light"
value="light"
label={t("theme_light")}
label={t("light")}
defaultChecked={user.theme === "light"}
register={formMethods.register}
/>
<ThemeLabel
variant="dark"
value="dark"
label={t("theme_dark")}
label={t("dark")}
defaultChecked={user.theme === "dark"}
register={formMethods.register}
/>
</div>
<hr className="border-subtle my-8 border [&:has(+hr)]:hidden" />
<BookerLayoutSelector
name="metadata.defaultBookerLayouts"
title={t("bookerlayout_user_settings_title")}
description={t("bookerlayout_user_settings_description")}
/>
<hr className="border-subtle my-8 border" />
<div className="mb-6 flex items-center text-sm">
<div>
@@ -244,7 +263,8 @@ const AppearanceView = () => {
type="submit"
loading={mutation.isLoading}
color="primary"
className="mt-8">
className="mt-8"
data-testid="update-theme-btn">
{t("update")}
</Button>
</Form>
@@ -149,7 +149,7 @@ const ConferencingLayout = () => {
});
}
}}>
{t("change_default_conferencing_app")}
{t("set_as_default")}
</DropdownItem>
</DropdownMenuItem>
)}
@@ -46,7 +46,10 @@ interface GeneralViewProps {
user: RouterOutputs["viewer"]["me"];
}
const WithQuery = withQuery(trpc.viewer.public.i18n, undefined, { trpc: { context: { skipBatch: true } } });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const WithQuery = withQuery(trpc.viewer.public.i18n as any, undefined, {
trpc: { context: { skipBatch: true } },
});
const GeneralQueryView = () => {
const { t } = useLocale();
@@ -215,7 +215,6 @@ const ProfileView = () => {
extraField={
<div className="mt-8">
<UsernameAvailabilityField
user={user}
onSuccessMutation={async () => {
showToast(t("settings_updated_successfully"), "success");
await utils.viewer.me.invalidate();
@@ -0,0 +1,31 @@
import { useRouter } from "next/router";
import { AboutOrganizationForm } from "@calcom/features/ee/organizations/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WizardLayout, Meta } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
const AboutOrganizationPage = () => {
const { t } = useLocale();
const router = useRouter();
if (!router.isReady) return null;
return (
<>
<Meta title={t("about_your_organization")} description={t("about_your_organization_description")} />
<AboutOrganizationForm />
</>
);
};
const LayoutWrapper = (page: React.ReactElement) => {
return (
<WizardLayout currentStep={3} maxSteps={5}>
{page}
</WizardLayout>
);
};
AboutOrganizationPage.getLayout = LayoutWrapper;
AboutOrganizationPage.PageWrapper = PageWrapper;
export default AboutOrganizationPage;
@@ -0,0 +1,37 @@
import type { NextRouter } from "next/router";
import { useRouter } from "next/router";
import { AddNewTeamsForm } from "@calcom/features/ee/organizations/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WizardLayout, Meta } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
const AddNewTeamsPage = () => {
const { t } = useLocale();
const router = useRouter();
if (!router.isReady) return null;
return (
<>
<Meta title={t("create_your_teams")} description={t("create_your_teams_description")} />
<AddNewTeamsForm />
</>
);
};
AddNewTeamsPage.getLayout = (page: React.ReactElement, router: NextRouter) => (
<>
<WizardLayout
currentStep={5}
maxSteps={5}
isOptionalCallback={() => {
router.push(`/event-types`);
}}>
{page}
</WizardLayout>
</>
);
AddNewTeamsPage.PageWrapper = PageWrapper;
export default AddNewTeamsPage;
@@ -0,0 +1,38 @@
import type { NextRouter } from "next/router";
import { useRouter } from "next/router";
import { AddNewOrgAdminsForm } from "@calcom/features/ee/organizations/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WizardLayout, Meta } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
const OnboardTeamMembersPage = () => {
const { t } = useLocale();
const router = useRouter();
if (!router.isReady) return null;
return (
<>
<Meta
title={t("invite_organization_admins")}
description={t("invite_organization_admins_description")}
/>
<AddNewOrgAdminsForm />
</>
);
};
OnboardTeamMembersPage.getLayout = (page: React.ReactElement, router: NextRouter) => (
<WizardLayout
currentStep={4}
maxSteps={5}
isOptionalCallback={() => {
router.push(`/settings/organizations/${router.query.id}/add-teams`);
}}>
{page}
</WizardLayout>
);
OnboardTeamMembersPage.PageWrapper = PageWrapper;
export default OnboardTeamMembersPage;

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