Compare commits

..
112 Commits
Author SHA1 Message Date
Nafees Nazik 9fa6aced33 feat: make key unique (#6861)
release.yml / release (push) Canceled after 0s
2023-02-03 11:20:45 +01:00
Hariom Balhara 9093909075 Hotfix: Event Name (#6863) (#6864) 2023-02-03 11:20:10 +01:00
Aaron Presley 832fce91e0 Left / Right buttons now bottom-aligned and slightly shorter (#6857)
Fixes https://github.com/calcom/cal.com/issues/6589
2023-02-02 20:49:20 +01:00
Peer Richelsen 6da41fce63 fixed availabilty layout 2023-02-02 19:28:46 +01:00
Alex van Andel 3b51cc9aef v2.5.8
release.yml / release (push) Canceled after 0s
2023-02-02 15:54:38 +00:00
Hariom Balhara 50c8959cfa Use trackingId only for trackingDomain as thats the only variable supported (#6856) 2023-02-02 15:13:42 +00:00
Hariom Balhara c96d258f5e Give highest possible value to z-index (#6853) 2023-02-02 15:04:38 +00:00
Alex van Andel 6e45b1c383 Don't override but decorate the built-in encode function (#6850)
* Don't override but decorate the built-in encode function

* Removed jose + synced lockfile
2023-02-02 14:26:00 +00:00
GitStart-Cal.comandgitstart-calcom 551ebe863b [CAL-90] Event Types Single / Allow multiple physical locations (#6692)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-02-02 12:42:56 +00:00
Peer Richelsen fa7b94bfe9 Merge branch 'main' of https://github.com/calcom/cal.com 2023-02-02 13:33:49 +01:00
Peer Richelsen 56c5d39cd3 i18n fix 2023-02-02 12:50:28 +01:00
Peer Richelsen 7574fb6f44 i18n fix 2023-02-02 12:49:11 +01:00
Nafees Nazik 901d1f0e01 fix: make cal video default value (#6716) 2023-02-02 12:24:34 +01:00
GitStart-Cal.comandgitstart-calcom 828fcbe1d9 [CAL-713] /availability single - layout issues. Check spacing, margin, alignment with spec'd design (#6724)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-02-02 12:21:05 +01:00
Joe Au-Yeung 34db46869e Move e2e to buildjet (#6840) 2023-02-02 08:42:28 +00:00
Nafees Nazik 1f1cab2be1 fix: padding in shell for mobile (#6825)
* fix: padding

* fix: breakpoints in fab

* fix: padding
2023-02-02 09:24:31 +01:00
Alex van Andel 6cf53feaf8 v2.5.7
release.yml / release (push) Canceled after 0s
2023-02-02 01:06:20 +00:00
Leo Giovanetti 376b693d91 Not allowing random duration (#6743) 2023-02-01 22:19:37 +00:00
Alex van Andel 831aa72d23 Invalidate hasTeamPlan when the last team is removed (#6837) 2023-02-01 21:23:34 +00:00
Peer RichelsenandAlex van Andel ef78db7856 /teams improvements (#6831)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-02-01 20:41:47 +00:00
Nafees Nazik 7ee5967075 feat: add theming options to teams (#6807) 2023-02-01 19:27:26 +01:00
Alex van Andel 5b165e6ebf Fixed broken path to fix SB build failure 2023-02-01 16:09:45 +00:00
Joe Shajan 624f52c305 webhook from ui design change (#6803)
* webhook from ui change

* fix: webhook test border & test webhook font style
2023-02-01 14:51:42 +00:00
GitStart-Cal.comandgitstart-calcom df25ae6a3d [CAL-845] Improve the custom event name modal UI/UX (#6660)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-02-01 13:10:16 +01:00
Josh Avant 7f65d7f3c3 Removes pay_later_instructions (#6824) 2023-02-01 09:17:11 +01:00
Alex van AndelandHariom Balhara c42674595c Add X-Frame-Options to page headers (#6784)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-02-01 11:28:47 +05:30
Nafees Nazikandalannnc 17eb45b0b6 fix: input component style in routing forms (#6731)
* fix: remove custom className

* removed extra type text

removed extra type text from input

---------

Co-authored-by: alannnc <alannnc@gmail.com>
2023-02-01 01:39:29 +00:00
Peer Richelsen 48d0edeaf4 fixed event-type description 2023-01-31 23:35:05 +01:00
Peer Richelsen ed4b31767f lint 2023-01-31 23:33:20 +01:00
Peer Richelsen cebb10bfe4 re-adding subtitle to Shell 2023-01-31 23:31:04 +01:00
Ben Lam 7a6e7c2f1d Removed unused variables (#6704) (#6750) 2023-01-31 22:29:24 +00:00
Peer Richelsen 0ffed24359 user management moved to console.cal.com (#6799) 2023-01-31 21:59:11 +00:00
Nafees NazikandAlan e121615d36 fix: installed apps and admin apps layout spacing issues. (#6753)
* feat: add highlight props

* feat: add isTemplate prop

* feat: add invalid credintial prop

* feat: add children prop

* feat: render app list card

* fix: add horizontal gap

* fix: installed app layout style

* fix: admin layout

* feat: add isGlobal to returned data

* feat: fix admin tab.

* use common components list and AppListCard

* show isDefault Badge

* hide switch for global apps

* fix: show switch

* fix: remove isglobal

* fix: layout

* refactor: remove unused component and import

* feat: use app list card component

* fix: query param

* fix: prevent unnecessary props passed to the component

* feat: add disabled style

* feat: add disconnect integration modal

* feat: new changes.

* render disconnect integration model only once.

* add dropdown for actions

* feat: changes in admin apps.

* use dropdown for selecting action buttons.

* use modal for editing keys.

* Remove boolean comparison

---------

Co-authored-by: Alan <alannnc@gmail.com>
2023-01-31 22:45:38 +01:00
4f9aa8bd96 Hide attendees from calendar description (#6800)
* Show the organizer timezone when booking on seats

* Only show organizer in event description

* Type fixes

* Update snapshot with new response

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Alan <alannnc@gmail.com>
2023-01-31 14:14:19 -07:00
alannncandsean-brydon 64ede37ad6 Fix embed modal leading inputs (#6822)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-01-31 13:58:15 -07:00
36b6c351ec Session Timeout (#6439)
* Implementation

* Unifying UI to password screen

* Tweaks for session in password section

* Update apps/web/pages/settings/security/password.tsx

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

* Update packages/features/kbar/Kbar.tsx

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

* Update packages/features/settings/layouts/SettingsLayout.tsx

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

* Relying on extra db query

* Upgrades typescript

* Update yarn lock

* Typings

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712)

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712) (#6713)

* Adds deployment settings to DB (#6706)

* WIP

* Adds DeploymentTheme

* Add missing migrations

* Adds client extensions for deployment

* Cleanup

* Using same version as other deps

* Reverting prisma changes and fixing things

* Uneeded tx-expect-error

* Fixing default value

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

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

---------

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-01-31 17:44:14 -03:00
GitStart-Cal.comandgitstart-calcom 9ab5a3149c [CAL-807] /event-types/single - Fix the header (#6738)
release.yml / release (push) Canceled after 0s
* [CAL-807] /event-types/single - Fix the header

* [CAL-807] /event-types/single - Fix the header

* [CAL-807] /event-types/single - Fix the header

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-01-31 20:19:02 +00:00
Nafees NazikandAlan 3b8f436bdf feat: tabs for teams in mobile (#6685)
* fix: padding

* feat: add teams tab component

* fix: add image to type

* fix: current path check

* fix: add avatar prop

* feat: add teams tab for mobile

* fix: padding

* fix: background and padding x

* fix: empty logic

* fix: conditions

* fix: bg

---------

Co-authored-by: Alan <alannnc@gmail.com>
2023-01-31 12:54:40 -07:00
Nafees Nazik 77d45a6ec2 fix: use guest email in create booking link banner (#6694) 2023-01-31 19:47:32 +00:00
Carina WollendorferandCarinaWolli 50c74cf286 change useHasTeamPlan hook to useHasPaidPlan (#6699)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-31 19:10:21 +00:00
Richard Poelderl dcbafced8b update isCalcom for canonicals to handle https://cal.com (#6802)
* update isCalcom for canonicals to handle https://cal.com

* avoid env var

* prettier

* handle window undefined

* handle window undefined during pre-render

* add some logs

* add some logs

* Revert "add some logs"

This reverts commit 9d27de029024e526417bc79e9cee42d7333ac592.
2023-01-31 19:07:50 +00:00
GitStart-Cal.comandgitstart-calcom 4679c80ea0 [CAL-727] [500] on /api/trpc/[trpc] (#6795)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-01-31 18:50:06 +00:00
Bram Verstraten 291316ee01 Fix Dutch custom event names (#6806)
Continued from #6375
2023-01-31 18:46:07 +00:00
sean-brydon 208fc2f0e4 Feat - invite user to team flow improvements (#6725) 2023-01-31 18:16:43 +00:00
Joe Au-Yeung 99e71054ff Change lint action to run on BuildJet (#6819) 2023-01-31 19:08:08 +01:00
Nafees Nazik 9d118d4e34 fix: flash of i18n in teams page (#6818) 2023-01-31 19:05:18 +01:00
+6 d1a877e70d Add additional guests (#6419)
* Add additional guests

* WIP

* WIP

* Type fix

* WIP

* Add validation for already invited attendees

* Revert type to email

* remove controlled fields

* disables signups via env variable (#6212)

* disables signups via env variable

* Apply suggestions from code review

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

* Reusable Upgrade component for teams only features (#6473)

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

* i18n udpate

* fix: darkmode in booking/[id] page (#6458)

* fix: text-area style

* fix: border styles

* fix: german translation for reschedule booking email  subject (#6483)

* fix: german translation for reschedule subject

* fix: add a full stop

* fix: app store spacing issues (#6481)

* feat: add equal spacing with gap

* fix: title style

* fix: title style

* fix: remove margin

* fix: remove margin

* fix: sub header spacing and slider button

* fix: category card text

* fix: app store spacing issues

* fix: tab in tablet

* fix: shell

* fix: styling of dropdown component (#6440)

* fix: styling of dropdown component

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

* Apply suggestions from code review

* fix: use dropdown item in all pages

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

* chore: undo

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>

* readding sidebar tips for everyone, added 3 more features to teams empty screen (#6475)

* readding sidebar tips for everyone

* added 3 more features to teams plan

* responsive fixes

* responsive fixes

* small responsive fix

* CTA alignment fixed

* Minor alignment fixes to Shell for event-types/[type]

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

* fix: wrong i18n  placeholder in many locales (#6496)

* fix: czech translation

* fix: spanish translation

* fix: italian translation

* fix: japanese translation

* fix: korean translation

* fix: dutch translation

* fix: norwegian translation

* fix: polish translation

* fix: portuguese translation

* fix: portuguese translation

* fix: romanian translation

* fix: russian translation

* fix: serbian translation

* fix: swedish translation

* fix: turkish translation

* fix: ukrainian translation

* fix: vietnamese translation

* fix: chinese simplified translation

* fix: chinese traditional translation

* Disable redirect for cancellation page (#6498)

* [CAL-677] /event-type : Event Setup sidebar item doesn't reflect multiple durations (#6390)

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

* Added fallback to hostsFixed too which is used in COLLECTIVE (#6500)

* Don't update the field when changing the select (#6486)

* Don't update the field when changing the select

* Fixing missing label

* Applied the correct y-8 vertical margin

* Changed the description vertical offset from title a bit more

* Add Trash icon on Availability list dropdown

* fix: tablet sidebar profile being too wide and not centered  (#6490)

* feat: updated app list items styling issues inside settings/conferencing

* feat: fixed tablet sidebar width and not centered issue

* fix: removed un-related changes from this pr

* deleted .env

* fix: added the accidentaly deleted .env file

* fix: removed unwanted .env file from the pr

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

* Bring back PublicShell (#6505)

* Add red text to error message

* WIP

* Rearrange guest input

* Fix validate unique guests logic

* Allow guests on reschedule

* Lint fix

* More lint fixes

* Lint fixes

* More lint fixes

* Fix remove guest button margin

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Jeff Loiselle <jeff@loiselles.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Nafees Nazik <84864519+G3root@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Ronit Panda <72537293+rtpa25@users.noreply.github.com>
Co-authored-by: Alan <alannnc@gmail.com>
2023-01-31 17:36:38 +00:00
Nafees Nazik 238b509be6 fix: border color in dark teams page (#6817) 2023-01-31 18:21:51 +01:00
Richard Poelderl c7471b404c fix(web): remove noindex & nofollow tags from the App and Shell components (#6816)
Fixes https://github.com/calcom/cal.com/issues/6815
2023-01-31 17:08:59 +00:00
Alex van Andel 4db4df8ace Correct invalid ukraine language code ua -> uk 2023-01-30 21:09:09 +00:00
Udit Takkar a15b8fb92e fix: booking limits saved as undefined (#6720)
* fix: booking limits saved as undefined

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

* fix: add event limit

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-30 19:38:22 +01:00
Syed Ali Shahbaz a4bed48eaf Standardize response codes in Login (#6787)
* standardizing login response codes

* playwright fix

* added translation
2023-01-30 18:37:03 +00:00
Peer Richelsen 1be6f192d4 added impersonation tip (#6794) 2023-01-30 16:19:11 +01:00
Richard Poelderl f1c5838aef fix(web): set canonical as https://cal.com in the <HeadSeo/> (#6693)
* fix(web): set canonical as https://cal.com in the <HeadSeo/>

* use `window.document.location?.href` if self hosted

* clean up

* use `isCalcom` from library

* handle trailing slash on homepage again
2023-01-30 13:54:07 +01:00
Peer Richelsen 8e994dcc33 adding ukrainian (#6791) 2023-01-30 13:49:08 +01:00
Peer Richelsen 506bbe3d31 Update README.md 2023-01-30 13:48:02 +01:00
Nafees Nazik 419a84c5a2 fix: location select menu overflowing the container (#6719) 2023-01-30 12:58:37 +01:00
Hariom Balhara 8cf0050d1f Fix: EventType Duplication (#6778) 2023-01-30 12:55:08 +01:00
Sai Deepesh 6c23813192 fix padding of lenght input on add event modal (#6771) 2023-01-30 11:12:53 +00:00
Joe Au-Yeung 875f922597 Disable autocomplete on password field (#6705) 2023-01-30 09:42:52 +00:00
Udit Takkar c8e3f6494a fix: back button and heading size (#6768)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-29 10:38:31 +01:00
Carina WollendorferandCarinaWolli 333801d2ae Fix shift-enter in bio editor (#6769)
* fix wrong enabled update button at first load

* fix shift-enter error

* small fixes + code clean up

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-27 23:14:58 +00:00
Joe Au-Yeung 60f7528cbc On seats, hide attendees if option is selected (#6767)
* On seats, hide attendees if option is selected

* Remove unused line
2023-01-27 12:10:49 -07:00
Peer Richelsen 0faf294367 removed credits from auth container (#6766) 2023-01-27 19:20:39 +01:00
Peer Richelsen 87c178e53b added blue color for links in bio (#6760) 2023-01-27 17:03:04 +01:00
Peer Richelsen 7710b1b718 Update README.md 2023-01-27 15:14:16 +01:00
Peer Richelsen d71c6ccb31 Update README.md 2023-01-27 12:07:12 +01:00
GitStart-Cal.comandgitstart-calcom 33a4117fb5 On duplicating an event type, spacing between arrows and label is wide (Length field) (#6717)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-01-27 11:06:29 +01:00
Omar López a90419c40b Adds booking data to SDK success event (#6737) 2023-01-27 11:02:33 +05:30
Omar López 16adf86c8c Upgrades typescript to 4.9.4 (#6747)
* Upgrades typescript to 4.9.4

* Type fixes
2023-01-26 18:50:56 -07:00
Peer Richelsen 3b838c475a replaced edit location dialog subtitle with link to app store (#6746) 2023-01-27 00:33:56 +01:00
Omar López cf33106d8b WIP: SIU: Maximum call stack size exceeded fix? (#6740)
* Maximum call stack size exceeded fix?

# Conflicts:
#	packages/prisma/index.ts

* Prisma client extensions is too much for our TS

* Removed unused Icons

* Type fixes
2023-01-26 17:51:03 -05:00
zomars a63f9ddf61 Adds deployment settings to DB (#6706) 2023-01-26 13:36:15 -07:00
zomars 1cf83068b2 Update yarn.lock 2023-01-26 13:33:59 -07:00
Hariom Balhara 23de92f818 Hotfix: ping,riverside,whereby and around not showing up in list (#6712) 2023-01-26 13:31:13 -07:00
zomars c6fc1f756b Update yarn.lock 2023-01-25 16:56:44 -07:00
zomars 88b7c506f1 Include submodule dependencies in lockfile 2023-01-25 16:56:25 -07:00
zomars 755aae969c Removed pre-install script
devs should opt-in to submodules
2023-01-25 13:39:12 -07:00
Peer Richelsen 2d50d09ce6 added hacker news badge to readme 2023-01-25 13:52:08 +01:00
Peer Richelsen a22397dbb4 Update README.md 2023-01-25 13:44:28 +01:00
Mohammed Cherfaoui d1076982a7 fix: event location link modal (#6387) 2023-01-25 09:49:05 +00:00
Nafees Nazik d77e4f3c4a fix: decode characters if encoded (#6447) 2023-01-25 09:44:48 +00:00
Joe Cohen b1c0ea4c38 Fix duplicate event modal page slug (#6657) 2023-01-25 09:16:50 +00:00
Carina WollendorferandCarinaWolli 425e18f53c enable scroll (#6676)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-25 09:14:20 +00:00
Peer Richelsen 3611807838 Update README.md (#6682) 2023-01-25 09:13:33 +00:00
Udit Takkar 5b1cd9e5ce fix: send email if username is being passed (#6674)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-25 08:56:20 +00:00
Joe Au-Yeung 00bff598ef Add videoCallData to evt on new attendee (#6673)
* Add videoCallData to evt on new attendee

* Remove console.log
2023-01-25 08:52:58 +00:00
Nafees Nazik 3a8aa26c78 fix: duplicate iframes in cal video (#6662)
* chore: upgrade daily-co

* feat: return only needed data and preload i18n

* fix: useEffect
2023-01-25 08:51:09 +00:00
Carina WollendorferandCarinaWolli 3c761f4451 Fixes issues with workflows that have more than one action (#6670)
* fix untitled workflow name when no action exists

* fix wrong data loaded one first laod when step was added

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-25 08:42:57 +00:00
8640eb9f52 Ability to format user and team bio (#6572)
* use texteditor for bio

* remove block types from about editor

* add props to make editor height adjustable

* set isDirty to true when about input is edited

* add editor to getting-started

* fix editor height

* remove required error for onboarding

* add helper function to check if parsed bio has text

* add back commented code

* fix onboarding tests for optional about field

* rename function

* parse team bio for read only members

* code clean up

* fix failing e2e because of missing test id

* fix onboarding e2e test

* add missing parse of user bio

* Update apps/web/components/getting-started/steps-views/UserProfile.tsx

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

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

* use css inline style for height instead of tailwind class

* fix height of editor-input

* save bio as markdown in db

* fix empty line when bio is empty

* fix hydration failed error

* Update packages/ui/components/editor/Editor.tsx

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

* remove unused import

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-24 18:08:10 -07:00
Kiran K abb5fd36f3 Support OIDC (#6661)
* Support OIDC

* tweak to the Configure button

* fix the type import
2023-01-24 13:02:43 -07:00
Alex van Andel 865663646a v2.5.5
release.yml / release (push) Canceled after 0s
2023-01-24 16:54:35 +00:00
Alex van Andel b7c92063c2 Revert "v2.5.4"
This reverts commit 4f293edb6b.
2023-01-24 16:54:05 +00:00
Alex van Andel 4f293edb6b v2.5.4 2023-01-24 16:52:33 +00:00
Ben HybertandAlex van Andel 0c88cf75e0 corrected avatars inside users block (#6667)
* corrected avatars inside users block

* ensured the team avatar didnt shrink too

* Update packages/ui/components/avatar/Avatar.tsx

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-24 16:10:43 +00:00
Nafees NazikandUdit Takkar 0b93647209 fix: double border issue in Location Select (#6646)
* fix: border issue

* fix: remove border from dropdown

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-01-24 17:05:35 +01:00
Alex van Andel b931b00714 Add env variables to turbo.json relevant to the website build (#6654) 2023-01-24 15:47:46 +00:00
Nafees Nazik 796a5f8d51 feat: use cal video as the default location (#6643)
* feat: use cal video as default while creating new events

* feat: use cal video as default value

* chore: remove default value

* fix: default value logic

* chore: remove console.log
2023-01-24 15:28:40 +00:00
sean-brydon 905a110377 Invites (#6665) 2023-01-24 15:27:05 +00:00
Alex van Andel 6ccc19985f Add sizePropsBySize to avatarPrimitive root (#6666) 2023-01-24 14:01:27 +00:00
Udit Takkar 8f71474303 fix: add allowOverflow property (#6636)
* fix: add allowOverlfow property

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

* fix: date override input dialog

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-24 14:00:45 +00:00
zomars 5f351230ff Ignores auth submodule 2023-01-23 16:50:17 -07:00
Alex van Andel 3e005919c1 Only return enabled apps in getEnabledApps (#6653) 2023-01-23 16:27:20 -07:00
cfa8fd8b67 Reduce bundle size by importing single icons at a time (#6644)
* Removed barrel import for icons to reduce bundle size.

* Fixed replacement mistakes

* Reverted unneccesary yarn.lock updates

* Added some missed Icon. import conversions

* Remove merge artifact import in @calcom/ui

* Don't import Icon in pages/[user]

* Update packages/ui/package.json

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
2023-01-23 23:08:01 +00:00
Alex van Andel a888be6e04 Support both https/git by detection of origin root (#6651)
* Support both https/git by detection of origin root

* Remove unnecessary .

* Minor comment fixup
2023-01-23 23:05:53 +00:00
Joe Au-Yeung 6aa2cba3ce Msteams access token refresh (#6607)
* Add is valid call

* WIP

* Change logic for Microsoft refresh token

* remove async
2023-01-23 15:36:24 -07:00
sean-brydon 12ab615684 Fix weird avatar issue (#6645) 2023-01-23 15:48:40 +00:00
1d792a2c6c generic <UpgradeScreen> component (#6594)
* first attempt of <UpgradeScreen>

* changes to icons

* reverted changes back to initial state, needs fix: teams not showing

* WIP

* Fix weird reactnode error

* Fix loading text

* added upgradeTip to routing forms

* icon colors

* create and use hook to check if user has team plan

* use useTeamPlan for upgradeTeamsBadge

* replace huge svg with compressed jpeg

* responsive fixes

* Update packages/ui/components/badge/UpgradeTeamsBadge.tsx

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

* Give team plan features to E2E tests

* Allow option to make a user part of team int ests

* Remove flash of paywall for team user

* Add team user for typeform tests as well

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-01-23 10:58:41 +01:00
Hariom Balhara 9602ac29fb Fix Ga4 tracking (#6630) 2023-01-23 09:09:16 +00:00
Leo Giovanetti 5714471b9a Temporary fix (#6626)
release.yml / release (push) Canceled after 0s
2023-01-22 17:27:27 +00:00
Mohammed CherfaouiandAlex van Andel a99e93a4bd fix tooltip and modal stacking issues (#6491)
* fix tooltip and modal stacking issues

* use z-index in larger screens and less

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-22 20:31:25 +05:30
Udit Takkar 7f044ff92f fix: use items-center (#6618) 2023-01-22 13:36:07 +01:00
357 changed files with 5517 additions and 2729 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ concurrency:
jobs:
e2e:
timeout-minutes: 20
runs-on: ubuntu-latest
runs-on: buildjet-4vcpu-ubuntu-2204
env:
DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
+1 -1
View File
@@ -11,7 +11,7 @@ concurrency:
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
runs-on: buildjet-4vcpu-ubuntu-2204
steps:
- name: Checkout repo
+1
View File
@@ -86,3 +86,4 @@ apps/storybook/build-storybook.log
apps/api
apps/website
apps/console
apps/auth
+28 -8
View File
@@ -27,14 +27,13 @@
<a href="https://www.producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
<a href="https://status.cal.com"><img src="https://betteruptime.com/status-badges/v1/monitor/a9kf.svg" alt="Uptime"></a>
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
<a href="https://news.ycombinator.com/item?id=26817795"><img src="https://img.shields.io/badge/Hacker%20News-311-%23FF6600" alt="Hacker News"></a>
<a href="https://news.ycombinator.com/item?id=34507672"><img src="https://img.shields.io/badge/Hacker%20News-%231-%23FF6600" alt="Hacker News"></a>
<a href="https://github.com/calcom/cal.com/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License"></a>
<a href="https://github.com/calcom/cal.com/pulse"><img src="https://img.shields.io/github/commit-activity/m/calcom/cal.com" alt="Commits-per-month"></a>
<a href="https://cal.com/pricing"><img src="https://img.shields.io/badge/Pricing-Free-brightgreen" alt="Pricing"></a>
<a href="https://jitsu.com?utm_source=github/calcom/cal.com"><img src="https://img.shields.io/badge/Metrics_tracked_by-JITSU-AA00FF?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACKSURBVHgBrZDRCYAwDEQv6gCOoKO4hOCXI9QVnEZwiY5iF5GaVClaBNtioCSUvCR3tMJaxIfZgW4AGUoEPVwgPZoS0Dmgg3NBVDFNbMIsmYCak3J1jDk9iCQvsKJvkzr71N81Gj6vDT/LU2P6RhY63jcafk3YJEbgeZpiFyc/5HJKv8Ef273NSfABGbQfUZhnOSAAAAAASUVORK5CYII=" alt="Jitsu Tracked"></a>
<img src="https://api.checklyhq.com/v1/badges/checks/5e048048-1b51-47ba-9209-60607507622e?responseTime=true" alt="Checkly Availability" />
<a href="https://hub.docker.com/r/calendso/calendso"><img src="https://img.shields.io/docker/pulls/calendso/calendso"></a>
<a href="https://twitter.com/calcom"><img src="https://img.shields.io/twitter/follow/calcom?style=flat"></a>
<a href="https://twitch.tv/calcomtv"><img src="https://img.shields.io/twitch/status/calcomtv?style=flat"></a>
<a href="https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
@@ -57,18 +56,39 @@ Calendly and other scheduling tools are awesome. It made our lives massively eas
That's where Cal.com comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data.
## Product of the Month: April 2021
## Recognition
#### Support us on [Product Hunt](https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso)
#### Hacker News
<a href="https://news.ycombinator.com/item?id=34507672">
<img
style="width: 250px; height: 54px;" width="250" height="54"
alt="Featured on Hacker News"
src="https://hackernews-badge.vercel.app/api?id=34507672"
/>
</a>
<a href="https://news.ycombinator.com/item?id=26817795">
<img
style="width: 250px; height: 54px;" width="250" height="54"
alt="Featured on Hacker News"
src="https://hackernews-badge.vercel.app/api?id=26817795"
/>
</a>
#### [Product Hunt](https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso)
<a href="https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://cal.com/maker-grant.svg" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
### Built With
- [Next.js](https://nextjs.org/)
- [React](https://reactjs.org/)
- [Tailwind](https://tailwindcss.com/)
- [Prisma](https://prisma.io/)
- [Next.js](https://nextjs.org/?ref=cal.com)
- [tRPC](https://trpc.io/?ref=cal.com)
- [React](https://reactjs.org/?ref=cal.com)
- [Tailwind](https://tailwindcss.com/?ref=cal.com)
- [Prisma](https://prisma.io/?ref=cal.com)
## Stay Up-to-Date
-80
View File
@@ -1,81 +1 @@
<!-- PROJECT LOGO -->
<div align="right">
<a href="https://github.com/calcom/cal.com">
<img src="https://user-images.githubusercontent.com/8019099/133430653-24422d2a-3c8d-4052-9ad6-0580597151ee.png" alt="Logo">
</a>
<a href="https://cal.com">Website</a>
·
<a href="https://github.com/calcom/cal.com/issues">Community Support</a>
</div>
# Cal.com Documentation
The official product, support and developer documentation, containing information and guides about using the product as well as support for self-hosted installations. This documentation site runs on [Nextra](https://nextra.vercel.app), so you may refer to their documentation should you need information on anything that isn't covered here.
## Prerequisites
- Git
- Node.js & npm
- Yarn
## Installation
Firstly, clone the repository using Git:
```console
git clone https://github.com/calcom/docs.git
```
Now, you can install the dependencies with yarn:
```console
yarn install
```
## Editing
To create, edit and delete documentation pages, you can simply create markdown (.mdx) files in the `pages/` folder. You can edit Markdown with any text editor, but VS Code and WebStorm have side-by-side previews so you can see your formatted content whilst writing markdown.
You will also need to add it as an entry to the `meta.json` file found in whichever directory that the .mdx file is in.
## Local Development
```console
yarn dev
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
## Build
```console
yarn build
```
This command generates static content into the `build` directory and can be served using any static content hosting service.
## How to easily contribute
## Existing Page
1. From the documentation's GitHub repository, head to the folder called 'pages' and open it.
2. From here you can view all current pages on the documentation site. Select the page you would like to contribute to.
3. You should now be able to view the page you have selected. Located at the top right of the page will be a pencil icon. Pressing this will bring you up an editor to edit and make changes. You can add formatting using the buttons at the top, which will automatically insert the relevant markdown content needed to style the text.
4. From here make the changes you wish to make.
5. At the bottom of the screen will be a 'Propose Changes' box, fill in all the relevant details such as title and description then press the green 'Propose Changes' button.
6. Your changes have been saved, to submit them for review, located on your screen, press the green 'Create Pull Request' button.
7. Fill in all the relevant details such as title and description and after finalize the submission.
You have now successfully edited and submitted changes to our documentation site.
## Creating a New Page
1. From the documentation's GitHub repository, head to the folder called 'pages' and open it.
2. From here you can view all current pages on the documentation site. At the top of your screen press the 'New file' button.
3. You should now be able to view the page you have created. Remember when renaming the document to put .mdx at the end of the file name.
4. From here make the changes you wish to make. Such as creating a title, sub-title and body text.
5. At the bottom of the screen will be a 'Propose Changes' box, fill in all the relevant details such as title and description then press the green 'Propose Changes' button.
6. Your changes have been saved, to submit them for review, located on your screen, press the greem 'Create Pull Request' button.
7. Fill in all the relevant details such as title and description and after finalize the submission.
You have now successfully created and submitted changes to our documentation site.
+1 -1
View File
@@ -49,7 +49,7 @@
"storybook-addon-next": "^1.6.9",
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.2.1",
"typescript": "^4.7.4",
"typescript": "^4.9.4",
"vite": "^2.9.15"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"@types/node": "16.9.1",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"typescript": "^4.7.4"
"typescript": "^4.9.4"
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { FiX } from "@calcom/ui/components/icon";
export default function AddToHomescreen() {
const { t } = useLocale();
@@ -40,7 +40,7 @@ export default function AddToHomescreen() {
type="button"
className="-mr-1 flex rounded-md p-2 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-white">
<span className="sr-only">{t("dismiss")}</span>
<Icon.FiX className="h-6 w-6 text-white" aria-hidden="true" />
<FiX className="h-6 w-6 text-white" aria-hidden="true" />
</button>
</div>
</div>
+65 -7
View File
@@ -1,35 +1,93 @@
import { ReactNode } from "react";
import { useRouter } from "next/router";
import { ReactNode, useEffect, useRef, useState } from "react";
import { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { Badge, ListItemText } from "@calcom/ui";
import { FiAlertCircle } from "@calcom/ui/components/icon";
interface AppListCardProps {
type ShouldHighlight = { slug: string; shouldHighlight: true } | { shouldHighlight?: never; slug?: never };
type AppListCardProps = {
logo?: string;
title: string;
description: string;
actions?: ReactNode;
isDefault?: boolean;
}
isTemplate?: boolean;
invalidCredential?: boolean;
children?: ReactNode;
} & ShouldHighlight;
const schema = z.object({ hl: z.string().optional() });
export default function AppListCard(props: AppListCardProps) {
const { t } = useLocale();
const { logo, title, description, actions, isDefault } = props;
const {
logo,
title,
description,
actions,
isDefault,
slug,
shouldHighlight,
isTemplate,
invalidCredential,
children,
} = props;
const {
data: { hl },
} = useTypedQuery(schema);
const router = useRouter();
const [highlight, setHighlight] = useState(shouldHighlight && hl === slug);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (shouldHighlight && highlight) {
const timer = setTimeout(() => {
setHighlight(false);
const url = new URL(window.location.href);
url.searchParams.delete("hl");
router.replace(url.pathname, undefined, { shallow: true });
}, 3000);
timeoutRef.current = timer;
}
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
}, []);
return (
<div className="p-4">
<div className="flex items-center gap-x-3">
<div className={`p-4 ${highlight ? "bg-yellow-100" : ""}`}>
<div className="flex gap-x-3">
{logo ? <img className="h-10 w-10" src={logo} alt={`${title} logo`} /> : null}
<div className="flex grow flex-col gap-y-1 truncate">
<div className="flex items-center gap-x-2">
<h3 className="truncate text-sm font-semibold text-gray-900">{title}</h3>
{isDefault ? <Badge variant="green">{t("default")}</Badge> : null}
<div className="flex items-center gap-x-2">
{isDefault && <Badge variant="green">{t("default")}</Badge>}
{isTemplate && <Badge variant="red">Template</Badge>}
</div>
</div>
<ListItemText component="p">{description}</ListItemText>
{invalidCredential && (
<div className="flex gap-x-2 pt-2">
<FiAlertCircle className="h-8 w-8 text-red-500 sm:h-4 sm:w-4" />
<ListItemText component="p" className="whitespace-pre-wrap text-red-500">
{t("invalid_credential")}
</ListItemText>
</div>
)}
</div>
{actions}
</div>
{children && <div className="w-full">{children}</div>}
</div>
);
}
+10 -10
View File
@@ -12,7 +12,6 @@ import {
DialogClose,
DialogContent,
HorizontalTabs,
Icon,
InputLeading,
Label,
showToast,
@@ -20,6 +19,7 @@ import {
TextArea,
TextField,
} from "@calcom/ui";
import { FiCode, FiTrello, FiSun, FiArrowLeft, FiChevronRight } from "@calcom/ui/components/icon";
import ColorPicker from "@components/ui/colorpicker";
import Select from "@components/ui/form/Select";
@@ -488,7 +488,7 @@ const tabs = [
{
name: "HTML",
href: "embedTabName=embed-code",
icon: Icon.FiCode,
icon: FiCode,
type: "code",
Component: forwardRef<
HTMLTextAreaElement | HTMLIFrameElement | null,
@@ -541,7 +541,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
{
name: "React",
href: "embedTabName=embed-react",
icon: Icon.FiCode,
icon: FiCode,
type: "code",
Component: forwardRef<
HTMLTextAreaElement | HTMLIFrameElement | null,
@@ -581,7 +581,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, prev
{
name: "Preview",
href: "embedTabName=embed-preview",
icon: Icon.FiTrello,
icon: FiTrello,
type: "iframe",
Component: forwardRef<
HTMLIFrameElement | HTMLTextAreaElement | null,
@@ -617,7 +617,7 @@ Cal("init", {origin:"${WEBAPP_URL}"});
const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => {
return (
<components.Control {...props}>
<Icon.FiSun className="ml-2 h-4 w-4 text-gray-500" />
<FiSun className="ml-2 h-4 w-4 text-gray-500" />
{children}
</components.Control>
);
@@ -815,7 +815,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
onClick={() => {
removeQueryParams(router, ["embedType", "embedTabName"]);
}}>
<Icon.FiArrowLeft className="mr-4 w-4" />
<FiArrowLeft className="mr-4 w-4" />
</button>
{embed.title}
</h3>
@@ -835,7 +835,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
? "Floating Popup Customization"
: "Element Click Customization"}
</div>
<Icon.FiChevronRight
<FiChevronRight
className={`${
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-gray-500`}
@@ -863,7 +863,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
};
});
}}
addOnLeading={<InputLeading>W</InputLeading>}
addOnLeading={<>W</>}
/>
<span className="p-2">×</span>
<TextField
@@ -883,7 +883,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
};
});
}}
addOnLeading={<InputLeading>H</InputLeading>}
addOnLeading={<>H</>}
/>
</div>
</div>
@@ -1002,7 +1002,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
<CollapsibleTrigger className="flex w-full" type="button">
<div className="text-base font-medium text-gray-900">Cal Booking Customization</div>
<Icon.FiChevronRight
<FiChevronRight
className={`${
isBookingCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-gray-500`}
+8 -7
View File
@@ -1,7 +1,8 @@
import React, { ComponentProps } from "react";
import Shell from "@calcom/features/shell/Shell";
import { ErrorBoundary, Icon } from "@calcom/ui";
import { ErrorBoundary } from "@calcom/ui";
import { FiCreditCard, FiKey, FiLock, FiTerminal, FiUser, FiUsers } from "@calcom/ui/components/icon";
import NavTabs from "./NavTabs";
@@ -9,32 +10,32 @@ const tabs = [
{
name: "profile",
href: "/settings/profile",
icon: Icon.FiUser,
icon: FiUser,
},
{
name: "teams",
href: "/settings/teams",
icon: Icon.FiUsers,
icon: FiUsers,
},
{
name: "security",
href: "/settings/security",
icon: Icon.FiKey,
icon: FiKey,
},
{
name: "developer",
href: "/settings/developer",
icon: Icon.FiTerminal,
icon: FiTerminal,
},
{
name: "billing",
href: "/settings/billing",
icon: Icon.FiCreditCard,
icon: FiCreditCard,
},
{
name: "admin",
href: "/settings/admin",
icon: Icon.FiLock,
icon: FiLock,
adminRequired: true,
},
];
@@ -4,7 +4,8 @@ import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
import { Button, Icon, Select } from "@calcom/ui";
import { Button, Select } from "@calcom/ui";
import { FiPlus } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
@@ -31,7 +32,7 @@ const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App
/>
) : (
<Button className="w-full" color="minimal" href="/apps/categories/calendar">
<Icon.FiPlus className="text-color mr-3 ml-1 h-4 w-4" />
<FiPlus className="text-color mr-3 ml-1 h-4 w-4" />
<p>{t("install_new_calendar_app")}</p>
</Button>
);
@@ -61,7 +62,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
<Select
name="additionalCalendar"
placeholder={
<Button StartIcon={Icon.FiPlus} color="secondary">
<Button StartIcon={FiPlus} color="secondary">
{t("add")}
</Button>
}
+21 -15
View File
@@ -12,7 +12,17 @@ import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/consta
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App";
import { Button, Icon, showToast, SkeletonButton, SkeletonText, HeadSeo, Badge } from "@calcom/ui";
import { Button, showToast, SkeletonButton, SkeletonText, HeadSeo, Badge } from "@calcom/ui";
import {
FiBookOpen,
FiCheck,
FiExternalLink,
FiFile,
FiFlag,
FiMail,
FiPlus,
FiShield,
} from "@calcom/ui/components/icon";
const Component = ({
name,
@@ -118,7 +128,7 @@ const Component = ({
isGlobal ||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
<div className="flex space-x-3">
<Button StartIcon={Icon.FiCheck} color="secondary" disabled>
<Button StartIcon={FiCheck} color="secondary" disabled>
{existingCredentials.length > 0
? t("active_install", { count: existingCredentials.length })
: t("default")}
@@ -139,7 +149,7 @@ const Component = ({
}
return (
<Button
StartIcon={Icon.FiPlus}
StartIcon={FiPlus}
{...props}
// @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now.
@@ -200,7 +210,7 @@ const Component = ({
</span>
)}
<div className="prose prose-sm mt-8">{body}</div>
<div className="prose-sm prose mt-8">{body}</div>
<h4 className="mt-8 font-semibold text-gray-900 ">{t("pricing")}</h4>
<span>
{price === 0 ? (
@@ -226,7 +236,7 @@ const Component = ({
rel="noreferrer"
className="text-sm font-normal text-black no-underline hover:underline"
href={docs}>
<Icon.FiBookOpen className="mr-1 -mt-1 inline h-4 w-4 text-gray-500" />
<FiBookOpen className="mr-1 -mt-1 inline h-4 w-4 text-gray-500" />
{t("documentation")}
</a>
</li>
@@ -238,7 +248,7 @@ const Component = ({
rel="noreferrer"
className="font-normal text-black no-underline hover:underline"
href={website}>
<Icon.FiExternalLink className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
<FiExternalLink className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{website.replace("https://", "")}
</a>
</li>
@@ -250,7 +260,7 @@ const Component = ({
rel="noreferrer"
className="font-normal text-black no-underline hover:underline"
href={"mailto:" + email}>
<Icon.FiMail className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
<FiMail className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{email}
</a>
@@ -263,7 +273,7 @@ const Component = ({
rel="noreferrer"
className="font-normal text-black no-underline hover:underline"
href={tos}>
<Icon.FiFile className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
<FiFile className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{t("terms_of_service")}
</a>
</li>
@@ -275,7 +285,7 @@ const Component = ({
rel="noreferrer"
className="font-normal text-black no-underline hover:underline"
href={privacy}>
<Icon.FiShield className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
<FiShield className="mr-1 -mt-px inline h-4 w-4 text-gray-500" />
{t("privacy_policy")}
</a>
</li>
@@ -286,7 +296,7 @@ const Component = ({
{t("every_app_published", { appName: APP_NAME, companyName: COMPANY_NAME })}
</span>
<a className="mt-2 block text-xs text-red-500" href={`mailto:${SUPPORT_MAIL_ADDRESS}`}>
<Icon.FiFlag className="inline h-3 w-3" /> {t("report_app")}
<FiFlag className="inline h-3 w-3" /> {t("report_app")}
</a>
</div>
</div>
@@ -321,15 +331,11 @@ export default function App(props: {
const { t } = useLocale();
return (
<Shell large isPublic heading={t("app_store")} backPath="/apps" withoutSeo>
<Shell smallHeading isPublic heading={t("app_store")} backPath="/apps" withoutSeo>
<HeadSeo
title={props.name}
description={props.description}
app={{ slug: props.logo, name: props.name, description: props.description }}
nextSeoProps={{
nofollow: true,
noindex: true,
}}
/>
{props.licenseRequired ? (
<LicenseRequired>
@@ -11,18 +11,18 @@ import {
Alert,
Button,
EmptyScreen,
Icon,
List,
showToast,
AppSkeletonLoader as SkeletonLoader,
Switch,
ShellSubHeading,
} from "@calcom/ui";
import { FiArrowLeft, FiCalendar, FiPlus } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
import AppListCard from "@components/AppListCard";
import AdditionalCalendarSelector from "@components/apps/AdditionalCalendarSelector";
import IntegrationListItem from "@components/apps/IntegrationListItem";
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
type Props = {
@@ -100,7 +100,7 @@ function CalendarSwitch(props: {
/>
{!!props.destination && (
<span className="ml-4 inline-flex items-center gap-1 rounded-md bg-gray-100 px-2 py-1 text-sm font-normal text-gray-800">
<Icon.FiArrowLeft className="h-4 w-4" />
<FiArrowLeft className="h-4 w-4" />
{t("adding_events_to")}
</span>
)}
@@ -118,13 +118,13 @@ function CalendarList(props: Props) {
success={({ data }) => (
<List>
{data.items.map((item) => (
<IntegrationListItem
name={item.name}
slug={item.slug}
key={item.title}
title={item.title}
<AppListCard
title={item.name}
key={item.name}
logo={item.logo}
description={item.description}
shouldHighlight
slug={item.slug}
actions={
<InstallAppButton
type={item.type}
@@ -161,16 +161,16 @@ function ConnectedCalendarsList(props: Props) {
}
return (
<List className="flex flex-col gap-6" noBorderTreatment>
<List>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars ? (
<IntegrationListItem
<AppListCard
shouldHighlight
slug={item.integration.slug}
title={item.integration.title}
title={item.integration.name}
logo={item.integration.logo}
description={item.primary?.email ?? item.integration.description}
separate={true}
actions={
<div className="flex w-32 justify-end">
<DisconnectIntegration
@@ -200,7 +200,7 @@ function ConnectedCalendarsList(props: Props) {
</>
)}
</div>
</IntegrationListItem>
</AppListCard>
) : (
<Alert
severity="warning"
@@ -282,8 +282,8 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
<div className="flex justify-between rounded-md border border-gray-200 bg-gray-50 p-4">
<div className="flex w-full flex-col items-start gap-4 md:flex-row md:items-center">
<div className="relative rounded-md border border-gray-200 bg-white p-1.5">
<Icon.FiCalendar className="h-8 w-8" strokeWidth="1" />
<Icon.FiPlus
<FiCalendar className="h-8 w-8" strokeWidth="1" />
<FiPlus
className="absolute left-4 top-1/2 ml-0.5 mt-[1px] h-2 w-2 text-black"
strokeWidth="4"
/>
@@ -322,7 +322,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
</>
) : (
<EmptyScreen
Icon={Icon.FiCalendar}
Icon={FiCalendar}
headline={t("no_category_apps", {
category: t("calendar").toLowerCase(),
})}
@@ -1,80 +0,0 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { ReactNode, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Badge, Icon, ListItem, ListItemText, ListItemTitle, showToast } from "@calcom/ui";
import classNames from "@lib/classNames";
function IntegrationListItem(props: {
imageSrc?: string;
slug: string;
name?: string;
title?: string;
description: string;
actions?: ReactNode;
children?: ReactNode;
logo: string;
destination?: boolean;
separate?: boolean;
invalidCredential?: boolean;
isTemplate?: boolean;
}): JSX.Element {
const { t } = useLocale();
const router = useRouter();
const { hl } = router.query;
const [highlight, setHighlight] = useState(hl === props.slug);
const title = props.name || props.title;
// The highlight is to show a newly installed app, coming from the app's
// redirection after installation, so we proceed to show the corresponding
// message
if (highlight) {
showToast(t("app_successfully_installed"), "success");
}
useEffect(() => {
const timer = setTimeout(() => setHighlight(false), 3000);
return () => {
clearTimeout(timer);
};
}, []);
return (
<ListItem
expanded={!!props.children}
className={classNames(
props.separate ? "rounded-md" : "first:rounded-t-md last:rounded-b-md",
"my-0 flex-col border transition-colors duration-500",
highlight ? "bg-yellow-100" : ""
)}>
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-4 rtl:space-x-reverse")}>
{props.logo && <img className="h-11 w-11" src={props.logo} alt={title} />}
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3" className="flex ">
<Link href={"/apps/" + props.slug}>{props.name || title}</Link>
{props.isTemplate && (
<Badge variant="red" className="ml-4">
Template
</Badge>
)}
</ListItemTitle>
<ListItemText component="p">{props.description}</ListItemText>
{/* Alert error that key stopped working. */}
{props.invalidCredential && (
<div className="flex items-center space-x-2 rtl:space-x-reverse">
<Icon.FiAlertCircle className="w-8 text-red-500 sm:w-4" />
<ListItemText component="p" className="whitespace-pre-wrap text-red-500">
{t("invalid_credential")}
</ListItemText>
</div>
)}
</div>
<div>{props.actions}</div>
</div>
{props.children && <div className="w-full">{props.children}</div>}
</ListItem>
);
}
export default IntegrationListItem;
@@ -4,7 +4,8 @@ import React, { ComponentProps } from "react";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen, Icon } from "@calcom/ui";
import { EmptyScreen } from "@calcom/ui";
import { FiAlertCircle } from "@calcom/ui/components/icon";
type AppsLayoutProps = {
children: React.ReactNode;
@@ -25,7 +26,7 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
<main className="w-full">
{emptyStore ? (
<EmptyScreen
Icon={Icon.FiAlertCircle}
Icon={FiAlertCircle}
headline={t("no_apps")}
description={session.data?.user.role === "ADMIN" ? "You can enable apps in the settings" : ""}
buttonText={session.data?.user.role === "ADMIN" ? t("apps_settings") : ""}
@@ -4,39 +4,39 @@ import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavi
import { InstalledAppVariants } from "@calcom/app-store/utils";
import Shell from "@calcom/features/shell/Shell";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
import { FiBarChart, FiCalendar, FiCreditCard, FiGrid, FiShare2, FiVideo } from "@calcom/ui/components/icon";
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{
name: "calendar",
href: "/apps/installed/calendar",
icon: Icon.FiCalendar,
icon: FiCalendar,
},
{
name: "conferencing",
href: "/apps/installed/conferencing",
icon: Icon.FiVideo,
icon: FiVideo,
},
{
name: "payment",
href: "/apps/installed/payment",
icon: Icon.FiCreditCard,
icon: FiCreditCard,
},
{
name: "automation",
href: "/apps/installed/automation",
icon: Icon.FiShare2,
icon: FiShare2,
},
{
name: "analytics",
href: "/apps/installed/analytics",
icon: Icon.FiBarChart,
icon: FiBarChart,
},
{
name: "other",
href: "/apps/installed/other",
icon: Icon.FiGrid,
icon: FiGrid,
},
];
@@ -57,9 +57,7 @@ export default function InstalledAppsLayout({
return (
<Shell {...rest}>
<AppCategoryNavigation
baseURL="/apps/installed"
containerClassname="w-full xl:mx-5 xl:w-4/5 xl:max-w-2xl xl:pr-5">
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
{children}
</AppCategoryNavigation>
</Shell>
@@ -10,6 +10,7 @@ import { UserPermissionRole } from ".prisma/client";
export default function AdminLayout({
children,
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const session = useSession();
@@ -22,10 +23,11 @@ export default function AdminLayout({
}
}, [session, router]);
const isAppsPage = router.asPath.startsWith("/settings/admin/apps");
return (
<SettingsLayout {...rest}>
<div className="mx-auto flex max-w-4xl flex-row divide-y divide-gray-200">
<div className="flex flex-1 [&>*]:flex-1">
<div className={isAppsPage ? "min-w-0" : "flex flex-1 [&>*]:flex-1"}>
<ErrorBoundary>{children}</ErrorBoundary>
</div>
</div>
@@ -1,7 +1,8 @@
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Tooltip } from "@calcom/ui";
import { Tooltip } from "@calcom/ui";
import { FiLink } from "@calcom/ui/components/icon";
import { Props } from "./pages/AvailabilityPage";
@@ -10,16 +11,16 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
return locations.length ? (
<div className="dark:text-darkgray-600 mr-6 flex w-full flex-col space-y-2 break-words text-sm text-gray-600">
{locations.map((location) => {
{locations.map((location, index) => {
const eventLocationType = getEventLocationType(location.type);
if (!eventLocationType) {
// It's possible that the location app got uninstalled
return null;
}
return (
<div key={location.type} className="flex flex-row items-center text-sm font-medium">
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
{eventLocationType.iconUrl === "/link.svg" ? (
<Icon.FiLink className="dark:text-darkgray-600 ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 " />
<FiLink className="dark:text-darkgray-600 ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 " />
) : (
<img
src={eventLocationType.iconUrl}
@@ -4,7 +4,8 @@ import { FC, ReactNode, useEffect } from "react";
import dayjs from "@calcom/dayjs";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Badge } from "@calcom/ui";
import { Badge } from "@calcom/ui";
import { FiCheckSquare, FiClock, FiInfo } from "@calcom/ui/components/icon";
import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -93,7 +94,7 @@ const BookingDescription: FC<Props> = (props) => {
isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600"
)}>
<div>
<Icon.FiInfo
<FiInfo
className={classNames(
"ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]",
isBookingPage && "dark:text-darkgray-600 -mt-1 text-gray-500"
@@ -112,7 +113,7 @@ const BookingDescription: FC<Props> = (props) => {
isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600"
)}>
<div>
<Icon.FiCheckSquare className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<FiCheckSquare className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
</div>
{requiresConfirmationText}
</div>
@@ -123,9 +124,10 @@ const BookingDescription: FC<Props> = (props) => {
<div
className={classNames(
"flex flex-nowrap text-sm font-medium",
isBookingPage && "dark:text-darkgray-600 text-gray-600"
isBookingPage && "dark:text-darkgray-600 text-gray-600",
!eventType.metadata?.multipleDuration && "items-center"
)}>
<Icon.FiClock
<FiClock
className={classNames(
"min-h-4 min-w-4 ml-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]",
isBookingPage && "mt-[2px]"
@@ -18,7 +18,6 @@ import {
DialogContent,
DialogFooter,
DialogHeader,
Icon,
MeetingTimeInTimezones,
showToast,
TextArea,
@@ -26,6 +25,7 @@ import {
ActionType,
TableActions,
} from "@calcom/ui";
import { FiCheck, FiClock, FiMapPin, FiRefreshCcw, FiSend, FiSlash, FiX } from "@calcom/ui/components/icon";
import useMeQuery from "@lib/hooks/useMeQuery";
@@ -100,7 +100,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
setRejectionDialogIsOpen(true);
},
icon: Icon.FiSlash,
icon: FiSlash,
disabled: mutation.isLoading,
},
{
@@ -109,7 +109,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
bookingConfirm(true);
},
icon: Icon.FiCheck,
icon: FiCheck,
disabled: mutation.isLoading,
color: "primary",
},
@@ -135,7 +135,7 @@ function BookingListItem(booking: BookingItemProps) {
href: `/booking/${booking.uid}?cancel=true${
isTabRecurring && isRecurring ? "&allRemainingBookings=true" : ""
}`,
icon: Icon.FiX,
icon: FiX,
},
{
id: "edit_booking",
@@ -143,13 +143,13 @@ function BookingListItem(booking: BookingItemProps) {
actions: [
{
id: "reschedule",
icon: Icon.FiClock,
icon: FiClock,
label: t("reschedule_booking"),
href: `/reschedule/${booking.uid}`,
},
{
id: "reschedule_request",
icon: Icon.FiSend,
icon: FiSend,
iconClassName: "rotate-45 w-[16px] -translate-x-0.5 ",
label: t("send_reschedule_request"),
onClick: () => {
@@ -162,7 +162,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
setIsOpenLocationDialog(true);
},
icon: Icon.FiMapPin,
icon: FiMapPin,
},
],
},
@@ -179,7 +179,7 @@ function BookingListItem(booking: BookingItemProps) {
const RequestSentMessage = () => {
return (
<div className="ml-1 mr-8 flex text-gray-500" data-testid="request_reschedule_sent">
<Icon.FiSend className="-mt-[1px] w-4 rotate-45" />
<FiSend className="-mt-[1px] w-4 rotate-45" />
<p className="ml-2 ">{t("reschedule_request_sent")}</p>
</div>
);
@@ -455,7 +455,7 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
);
})}>
<div className="text-gray-600 dark:text-white">
<Icon.FiRefreshCcw
<FiRefreshCcw
strokeWidth="3"
className="float-left mr-1 mt-1.5 inline-block h-3 w-3 text-gray-400"
/>
@@ -5,7 +5,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Button, Icon, TextArea } from "@calcom/ui";
import { Button, TextArea } from "@calcom/ui";
import { FiX } from "@calcom/ui/components/icon";
type Props = {
booking: {
@@ -28,7 +29,7 @@ export default function CancelBooking(props: Props) {
const [cancellationReason, setCancellationReason] = useState<string>("");
const { t } = useLocale();
const router = useRouter();
const { booking, profile, team, allRemainingBookings } = props;
const { booking, allRemainingBookings } = props;
const [loading, setLoading] = useState(false);
const telemetry = useTelemetry();
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
@@ -39,7 +40,7 @@ export default function CancelBooking(props: Props) {
{error && (
<div className="mt-8">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" />
<FiX className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
@@ -27,8 +27,10 @@ import notEmpty from "@calcom/lib/notEmpty";
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import { Icon, HeadSeo } from "@calcom/ui";
import { HeadSeo } from "@calcom/ui";
import { FiChevronDown, FiChevronUp, FiCreditCard, FiGlobe, FiRefreshCcw } from "@calcom/ui/components/icon";
import { timeZone as localStorageTimeZone } from "@lib/clock";
import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -97,7 +99,10 @@ const SlotPicker = ({
weekStart = 0,
ethSignature,
}: {
eventType: Pick<EventType, "id" | "schedulingType" | "slug">;
eventType: Pick<
EventType & { metadata: z.infer<typeof EventTypeMetaDataSchema> },
"id" | "schedulingType" | "slug" | "length" | "metadata"
>;
timeFormat: TimeFormat;
onTimeFormatChange: (is24Hour: boolean) => void;
timeZone?: string;
@@ -109,11 +114,15 @@ const SlotPicker = ({
}) => {
const [selectedDate, setSelectedDate] = useState<Dayjs>();
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
const { duration } = useRouterQuery("duration");
let { duration = eventType.length.toString() } = useRouterQuery("duration");
const { date, setQuery: setDate } = useRouterQuery("date");
const { month, setQuery: setMonth } = useRouterQuery("month");
const router = useRouter();
if (!eventType.metadata?.multipleDuration) {
duration = eventType.length.toString();
}
const [slotPickerRef] = useAutoAnimate<HTMLDivElement>();
useEffect(() => {
@@ -220,12 +229,12 @@ function TimezoneDropdown({
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 !mt-2 inline-block self-start rounded-md px-2 py-2 text-left text-gray-600">
<p className="flex items-center text-sm font-medium">
<Icon.FiGlobe className="min-h-4 min-w-4 ml-[2px] -mt-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]" />
<FiGlobe className="min-h-4 min-w-4 ml-[2px] -mt-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]" />
{timeZone}
{isTimeOptionsOpen ? (
<Icon.FiChevronUp className="min-h-4 min-w-4 ml-1 inline-block" />
<FiChevronUp className="min-h-4 min-w-4 ml-1 inline-block" />
) : (
<Icon.FiChevronDown className="min-h-4 min-w-4 ml-1 inline-block" />
<FiChevronDown className="min-h-4 min-w-4 ml-1 inline-block" />
)}
</p>
</Popover.Trigger>
@@ -367,7 +376,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<FiRefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
@@ -392,7 +401,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
)}
{stripeAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
+155 -85
View File
@@ -6,9 +6,8 @@ import { useSession } from "next-auth/react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useMemo, useReducer, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { useFieldArray, useForm, useWatch, Controller } from "react-hook-form";
import { FormattedNumber, IntlProvider } from "react-intl";
import { ReactMultiEmail } from "react-multi-email";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
@@ -38,9 +37,29 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { HttpError } from "@calcom/lib/http-error";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import slugify from "@calcom/lib/slugify";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { AddressInput, Button, EmailInput, Form, Icon, PhoneInput, Tooltip } from "@calcom/ui";
import { Group, RadioField } from "@calcom/ui";
import {
AddressInput,
Button,
EmailField,
EmailInput,
Form,
Group,
PhoneInput,
RadioField,
Tooltip,
} from "@calcom/ui";
import {
FiUserPlus,
FiCalendar,
FiX,
FiInfo,
FiCreditCard,
FiRefreshCw,
FiUser,
FiAlertTriangle,
} from "@calcom/ui/components/icon";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
@@ -49,7 +68,6 @@ import useRouterQuery from "@lib/hooks/useRouterQuery";
import createBooking from "@lib/mutations/bookings/create-booking";
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
import { parseDate, parseRecurringDates } from "@lib/parseDate";
import slugify from "@lib/slugify";
import Gates, { Gate, GateState } from "@components/Gates";
import BookingDescription from "@components/booking/BookingDescription";
@@ -65,7 +83,7 @@ type BookingFormValues = {
email: string;
notes?: string;
locationType?: EventLocationType["type"];
guests?: string[];
guests?: { email: string }[];
address?: string;
attendeeAddress?: string;
phone?: string;
@@ -107,7 +125,12 @@ const BookingPage = ({
const stripeAppData = getStripeAppData(eventType);
// Define duration now that we support multiple duration eventTypes
let duration = eventType.length;
if (queryDuration && !isNaN(Number(queryDuration))) {
if (
queryDuration &&
!isNaN(Number(queryDuration)) &&
eventType.metadata?.multipleDuration &&
eventType.metadata?.multipleDuration.includes(Number(queryDuration))
) {
duration = Number(queryDuration);
}
@@ -184,7 +207,9 @@ const BookingPage = ({
const loggedInIsOwner = eventType?.users[0]?.id === session?.user?.id;
const guestListEmails = !isDynamicGroupBooking
? booking?.attendees.slice(1).map((attendee) => attendee.email)
? booking?.attendees.slice(1).map((attendee) => {
return { email: attendee.email };
})
: [];
// There should only exists one default userData variable for primaryAttendee.
@@ -203,7 +228,9 @@ const BookingPage = ({
name: defaultUserValues.name || (!loggedInIsOwner && session?.user?.name) || "",
email: defaultUserValues.email || (!loggedInIsOwner && session?.user?.email) || "",
notes: (router.query.notes as string) || "",
guests: ensureArray(router.query.guest) as string[],
guests: ensureArray(router.query.guest).map((guest) => {
return { email: guest as string };
}),
customInputs: eventType.customInputs.reduce(
(customInputs, input) => ({
...customInputs,
@@ -245,6 +272,7 @@ const BookingPage = ({
.object({
name: z.string().min(1),
email: z.string().trim().email(),
guests: z.array(z.object({ email: z.string().email() })).optional(),
phone: z
.string()
.refine((val) => isValidPhoneNumber(val))
@@ -263,6 +291,10 @@ const BookingPage = ({
defaultValues: defaultValues(),
resolver: zodResolver(bookingFormSchema), // Since this isn't set to strict we only validate the fields in the schema
});
const guestsField = useFieldArray({
name: "guests",
control: bookingForm.control,
});
const selectedLocationType = useWatch({
control: bookingForm.control,
@@ -301,6 +333,7 @@ const BookingPage = ({
}
const bookEvent = (booking: BookingFormValues) => {
bookingForm.clearErrors();
const bookingCustomInputs = Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && booking.customInputs[inputId] ? booking.customInputs[inputId] : "",
@@ -368,6 +401,29 @@ const BookingPage = ({
}
}
// Validate that guests are unique
let alreadyInvited = false;
booking.guests?.forEach((guest, index) => {
if (guest.email === booking.email) {
bookingForm.setError(`guests.${index}`, { type: "validate", message: t("already_invited") });
alreadyInvited = true;
}
if (booking.guests) {
let guestCount = 0;
for (const checkGuest of booking.guests) {
if (checkGuest.email === guest.email) guestCount++;
if (guestCount > 1) {
bookingForm.setError(`guests.${index}`, { type: "validate", message: t("already_invited") });
alreadyInvited = true;
break;
}
}
}
});
if (alreadyInvited) return;
if (recurringDates.length) {
// Identify set of bookings to one intance of recurring event to support batch changes
const recurringEventId = uuidv4();
@@ -398,6 +454,7 @@ const BookingPage = ({
? booking.phone
: booking.smsReminderNumber || undefined,
ethSignature: gateState.rainbowToken,
guests: booking.guests?.map((guest) => guest.email),
}));
recurringMutation.mutate(recurringBookings);
} else {
@@ -426,6 +483,7 @@ const BookingPage = ({
? booking.phone
: booking.smsReminderNumber || undefined,
ethSignature: gateState.rainbowToken,
guests: booking.guests?.map((guest) => guest.email),
});
}
};
@@ -479,7 +537,7 @@ const BookingPage = ({
})}{" "}
| {APP_NAME}
</title>
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favico.ico" />
</Head>
<BookingPageTagManager eventType={eventType} />
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
@@ -501,7 +559,7 @@ const BookingPage = ({
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
{stripeAppData.price > 0 && (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<Icon.FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
@@ -513,7 +571,7 @@ const BookingPage = ({
)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="items-start text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiRefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<FiRefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({
t,
@@ -524,7 +582,7 @@ const BookingPage = ({
</div>
)}
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiCalendar className="ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<FiCalendar className="ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid &&
@@ -552,14 +610,14 @@ const BookingPage = ({
{t("former_time")}
</p>
<p className="line-through ">
<Icon.FiCalendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<FiCalendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
{!!eventType.seatsPerTimeSlot && (
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiUser
<FiUser
className={`ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] ${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
@@ -623,7 +681,7 @@ const BookingPage = ({
/>
{bookingForm.formState.errors.email && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("email_validation_error")}</p>
</div>
)}
@@ -716,7 +774,7 @@ const BookingPage = ({
</div>
{bookingForm.formState.errors.phone && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p>
</div>
)}
@@ -834,7 +892,7 @@ const BookingPage = ({
/>
{bookingForm.formState.errors?.customInputs?.[input.id] && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p>
</div>
)}
@@ -842,60 +900,7 @@ const BookingPage = ({
)}
</div>
))}
{!eventType.disableGuests && guestToggle && (
<div className="mb-4">
<div>
<label
htmlFor="guests"
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{t("guests")}
</label>
{!disableInput && (
<Controller
control={bookingForm.control}
name="guests"
render={({ field: { onChange, value } }) => (
<ReactMultiEmail
className="relative"
placeholder={<span className="dark:text-darkgray-600">guest@example.com</span>}
emails={value}
onChange={onChange}
getLabel={(
email: string,
index: number,
removeEmail: (index: number) => void
) => {
return (
<div data-tag key={index} className="cursor-pointer">
{email}
{!disableInput && (
<span data-tag-handle onClick={() => removeEmail(index)}>
×
</span>
)}
</div>
);
}}
/>
)}
/>
)}
{/* Custom code when guest emails should not be editable */}
{disableInput && guestListEmails && guestListEmails.length > 0 && (
<div data-tag className="react-multi-email">
{/* // @TODO: user owners are appearing as guest here when should be only user input */}
{guestListEmails.map((email, index) => {
return (
<div key={index} className="cursor-pointer">
<span data-tag>{email}</span>
</div>
);
})}
</div>
)}
</div>
</div>
)}
{isSmsReminderNumberNeeded && selectedLocationType !== LocationType.Phone && (
<div className="mb-4">
<label
@@ -914,7 +919,7 @@ const BookingPage = ({
</div>
{bookingForm.formState.errors.smsReminderNumber && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p>
</div>
)}
@@ -948,19 +953,84 @@ const BookingPage = ({
/>
)}
</div>
{!eventType.disableGuests && guestsField.fields.length ? (
<div className="mb-4">
<div>
<label
htmlFor="guests"
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{t("guests")}
</label>
<ul>
{guestsField.fields.map((field, index) => (
<li key={field.id}>
<EmailField
{...bookingForm.register(`guests.${index}.email` as const)}
className={classNames(
inputClassName,
bookingForm.formState.errors.guests?.[index] &&
"!focus:ring-red-700 !border-red-700",
"border-r-0"
)}
addOnClassname={classNames(
"border-gray-300 border block border-l-0 disabled:bg-gray-200 disabled:hover:cursor-not-allowed bg-transparent disabled:text-gray-500 dark:border-darkgray-300 ",
bookingForm.formState.errors.guests?.[index] &&
"!focus:ring-red-700 !border-red-700"
)}
placeholder="guest@example.com"
label={<></>}
required
addOnSuffix={
<Tooltip content="Remove guest">
<button
className="m-1 disabled:hover:cursor-not-allowed"
type="button"
onClick={() => guestsField.remove(index)}>
<FiX className="text-gray-600" />
</button>
</Tooltip>
}
/>
{bookingForm.formState.errors.guests?.[index] && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p className="text-red-700">
{bookingForm.formState.errors.guests?.[index]?.message}
</p>
</div>
)}
</li>
))}
</ul>
<Button
type="button"
color="minimal"
StartIcon={FiUserPlus}
className="my-2.5"
// className="mb-1 block text-sm font-medium text-gray-700 dark:text-white"
onClick={() => guestsField.append({ email: "" })}>
{t("add_another")}
</Button>
</div>
</div>
) : (
<></>
)}
{!eventType.disableGuests && !guestsField.fields.length && (
<Button
color="minimal"
variant="button"
StartIcon={FiUserPlus}
onClick={() => {
guestsField.append({ email: "" });
}}
className="mr-auto">
{t("additional_guests")}
</Button>
)}
<div className="flex justify-end space-x-2 rtl:space-x-reverse">
{!eventType.disableGuests && !guestToggle && (
<Button
type="button"
color="minimal"
variant="icon"
tooltip={t("additional_guests")}
StartIcon={Icon.FiUserPlus}
onClick={() => setGuestToggle(!guestToggle)}
className="mr-auto"
/>
)}
<Button color="minimal" type="button" onClick={() => router.back()}>
{t("cancel")}
</Button>
@@ -993,7 +1063,7 @@ function ErrorMessage({ error }: { error: unknown }) {
<div data-testid="booking-fail" className="mt-2 border-l-4 border-yellow-400 bg-yellow-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<Icon.FiAlertTriangle className="h-5 w-5 text-yellow-400" aria-hidden="true" />
<FiAlertTriangle className="h-5 w-5 text-yellow-400" aria-hidden="true" />
</div>
<div className="ltr:ml-3 rtl:mr-3">
<p className="text-sm text-yellow-700">
@@ -1,6 +1,8 @@
import { ErrorMessage } from "@hookform/error-message";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { Trans } from "next-i18next";
import Link from "next/link";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { z } from "zod";
@@ -15,7 +17,8 @@ import {
} from "@calcom/app-store/locations";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogContent, DialogFooter, Form, Icon, PhoneInput } from "@calcom/ui";
import { Button, Dialog, DialogContent, DialogFooter, Form, PhoneInput } from "@calcom/ui";
import { FiMapPin } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
@@ -204,10 +207,10 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
return (
<Dialog open={isOpenDialog} onOpenChange={(open) => setShowLocationModal(open)}>
<DialogContent disableOverflow>
<DialogContent>
<div className="flex flex-row space-x-3">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
<FiMapPin className="text-primary-600 h-6 w-6" />
</div>
<div className="w-full">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
@@ -215,7 +218,15 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
{t("edit_location")}
</h3>
{!booking && (
<p className="text-sm text-gray-400">{t("this_input_will_shown_booking_this_event")}</p>
<p className="text-sm text-gray-400">
<Trans i18nKey="cant_find_the_right_video_app_visit_our_app_store">
Can&apos;t find the right video app? Visit our
<Link className="cursor-pointer text-blue-500 underline" href="/apps/categories/video">
App Store
</Link>
.
</Trans>
</p>
)}
</div>
<div className="mt-3 text-center sm:mt-0 sm:text-left" />
@@ -288,30 +299,31 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
name="locationType"
control={locationFormMethods.control}
render={() => (
<LocationSelect
maxMenuHeight={300}
name="location"
defaultValue={selection}
options={locationOptions}
isSearchable
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
<div className="py-4">
<LocationSelect
maxMenuHeight={300}
name="location"
defaultValue={selection}
options={locationOptions}
isSearchable
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
</div>
)}
/>
);
@@ -325,7 +337,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
setEditingLocationType?.("");
locationFormMethods.unregister("locationType");
locationFormMethods.unregister(["locationType", "locationLink"]);
}}
type="button"
color="secondary">
@@ -9,10 +9,10 @@ import {
DialogContent,
DialogFooter,
DialogHeader,
Icon,
showToast,
TextArea,
} from "@calcom/ui";
import { FiClock } from "@calcom/ui/components/icon";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -43,7 +43,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
<DialogContent>
<div className="flex flex-row space-x-3">
<div className="flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<Icon.FiClock className="m-auto h-6 w-6" />
<FiClock className="m-auto h-6 w-6" />
</div>
<div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} />
@@ -8,7 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Badge, Button, Icon, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { FiExternalLink, FiGlobe } from "@calcom/ui/components/icon";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
@@ -164,13 +165,13 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
<hr />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
<span className="flex items-center justify-center text-sm text-gray-600 sm:justify-start">
<Icon.FiGlobe className="ltr:mr-2 rtl:ml-2" />
<FiGlobe className="ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
<Button
href={`/availability/${schedule?.schedule.id}`}
color="minimal"
EndIcon={Icon.FiExternalLink}
EndIcon={FiExternalLink}
target="_blank"
className="justify-center border sm:border-0"
rel="noopener noreferrer">
@@ -5,7 +5,8 @@ import { FC } from "react";
import { Control, Controller, useFieldArray, useForm, UseFormRegister, useWatch } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, TextField } from "@calcom/ui";
import { Button, Label, Select, TextField } from "@calcom/ui";
import { FiPlus, FiX } from "@calcom/ui/components/icon";
interface OptionTypeBase {
label: string;
@@ -77,7 +78,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
defaultValue={selectedInputOption}
options={inputOptions}
isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 text-sm"
className="mt-1 mb-2 block w-full min-w-0 flex-1 text-sm"
onChange={(option) => option && field.onChange(option.value)}
value={selectedInputOption}
onBlur={field.onBlur}
@@ -185,7 +186,7 @@ function RadioInputHandler({
<Button
variant="icon"
color="minimal"
StartIcon={Icon.FiX}
StartIcon={FiX}
onClick={() => {
remove(index);
}}
@@ -196,7 +197,7 @@ function RadioInputHandler({
))}
<Button
color="minimal"
StartIcon={Icon.FiPlus}
StartIcon={FiPlus}
className="!text-sm !font-medium"
onClick={() => {
append({ label: "", type: "text" });
@@ -5,6 +5,8 @@ import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import type { EventNameObjectType } from "@calcom/core/event";
import { getEventName } from "@calcom/core/event";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import CustomInputItem from "@calcom/features/eventtypes/components/CustomInputItem";
import { APP_NAME, CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
@@ -18,13 +20,13 @@ import {
DialogClose,
DialogContent,
DialogFooter,
Icon,
Label,
SettingsToggle,
showToast,
TextField,
Tooltip,
} from "@calcom/ui";
import { FiEdit, FiCopy, FiPlus } from "@calcom/ui/components/icon";
import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm";
@@ -57,6 +59,14 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const eventNameObject: EventNameObjectType = {
attendeeName: t("scheduler"),
eventType: eventType.title,
eventName: eventType.eventName,
host: eventType.users[0]?.name || "Nameless",
t,
};
const [previewText, setPreviewText] = useState(getEventName(eventNameObject));
const [customInputs, setCustomInputs] = useState<CustomInputParsed[]>(
eventType.customInputs.sort((a, b) => a.id - b.id) || []
);
@@ -72,6 +82,15 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
setCustomInputs([...customInputs]);
};
const changePreviewText = (eventNameObject: EventNameObjectType, previewEventName: string) => {
setPreviewText(
previewEventName
.replace("{Event type title}", eventNameObject.eventType)
.replace("{Scheduler}", eventNameObject.attendeeName)
.replace("{Organiser}", eventNameObject.host)
);
};
useEffect(() => {
!hashedUrl && setHashedUrl(generateHashedLink(eventType.users[0]?.id ?? team?.id));
}, [eventType.users, hashedUrl, team?.id]);
@@ -120,15 +139,22 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
)}
<div className="w-full">
<TextField
label={t("event_name")}
label={t("event_name_in_calendar")}
type="text"
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
placeholder={t("meeting_with_user")}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
{...formMethods.register("eventName", {
onChange: (e) => {
if (!e.target.value || !formMethods.getValues("eventName")) {
return setPreviewText(getEventName(eventNameObject));
}
changePreviewText(eventNameObject, e.target.value);
},
})}
addOnSuffix={
<Button
type="button"
StartIcon={Icon.FiEdit}
StartIcon={FiEdit}
variant="icon"
color="minimal"
className="hover:stroke-3 min-w-fit px-0 hover:bg-transparent hover:text-black"
@@ -168,7 +194,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</ul>
{customInputs.length > 0 && (
<Button
StartIcon={Icon.FiPlus}
StartIcon={FiPlus}
color="minimal"
type="button"
onClick={() => {
@@ -314,7 +340,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}}
className="hover:stroke-3 hover:bg-transparent hover:text-black"
type="button">
<Icon.FiCopy />
<FiCopy />
</Button>
</Tooltip>
}
@@ -384,23 +410,63 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
description={t("custom_event_name_description")}
type="creation">
<TextField
label={t("event_name")}
label={t("event_name_in_calendar")}
type="text"
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
placeholder={t("meeting_with_user")}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
{...formMethods.register("eventName", {
onChange: (e) => {
if (!e.target.value || !formMethods.getValues("eventName")) {
return setPreviewText(getEventName(eventNameObject));
}
changePreviewText(eventNameObject, e.target.value);
},
})}
className="mb-0"
/>
<div className="mt-1 text-gray-500">
<p>{`{HOST} = ${t("your_name")}`}</p>
<p>{`{ATTENDEE} = ${t("attendee_name")}`}</p>
<p>{`{HOST/ATTENDEE} = ${t("dynamically_display_attendee_or_organizer")}`}</p>
<p>{`{LOCATION} = ${t("event_location")}`}</p>
<div className="text-sm">
<div className="mb-6 rounded-md bg-gray-100 p-2">
<h1 className="mb-2 ml-1 font-medium text-gray-900">{t("available_variables")}</h1>
<div className="mb-2.5 flex font-normal">
<p className="ml-1 mr-5 w-28 text-gray-400">{`{Event type title}`}</p>
<p className="text-gray-900">{t("event_name_info")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="ml-1 mr-5 w-28 text-gray-400">{`{Organiser}`}</p>
<p className="text-gray-900">{t("your_full_name")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="ml-1 mr-5 w-28 text-gray-400">{`{Scheduler}`}</p>
<p className="text-gray-900">{t("scheduler_full_name")}</p>
</div>
<div className="mb-1 flex font-normal">
<p className="ml-1 mr-5 w-28 text-gray-400">{`{Location}`}</p>
<p className="text-gray-900">{t("location_info")}</p>
</div>
</div>
<h1 className="mb-2 text-[14px] font-medium leading-4">{t("preview")}</h1>
<div
className="flex h-[212px] w-full rounded-md border-y bg-cover bg-center"
style={{
backgroundImage: "url(/calendar-preview.svg)",
}}>
<div className="m-auto flex items-center justify-center self-stretch">
<div className="mt-3 ml-11 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black bg-gray-100 text-[12px] leading-3">
<p className="overflow-hidden text-ellipsis p-1.5 font-medium text-gray-900">
{previewText}
</p>
<p className="ml-1.5 text-[10px] font-normal text-gray-600">8 - 10 AM</p>
</div>
</div>
</div>
</div>
<DialogFooter>
<DialogClose onClick={() => formMethods.setValue("eventName", eventType.eventName ?? "")}>
{t("cancel")}
</DialogClose>
<Button color="primary" onClick={() => setShowEventNameTip(false)}>
{t("create")}
</Button>
<DialogClose onClick={() => formMethods.setValue("eventName", eventType.eventName ?? "")} />
</DialogFooter>
</DialogContent>
</Dialog>
@@ -413,7 +479,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<Dialog open={selectedCustomInputModalOpen} onOpenChange={setSelectedCustomInputModalOpen}>
<DialogContent
type="creation"
Icon={Icon.FiPlus}
Icon={FiPlus}
title={t("add_new_custom_input_field")}
description={t("this_input_will_shown_booking_this_event")}>
<CustomInputTypeForm
@@ -7,7 +7,8 @@ import { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
import { EventTypeAppsList } from "@calcom/app-store/utils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, Icon } from "@calcom/ui";
import { Button, EmptyScreen } from "@calcom/ui";
import { FiGrid } from "@calcom/ui/components/icon";
export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
EventTypeAppCardComponentProps["eventType"];
@@ -60,7 +61,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
<div className="before:border-0">
{!isLoading && !installedApps?.length ? (
<EmptyScreen
Icon={Icon.FiGrid}
Icon={FiGrid}
headline={t("empty_installed_apps_headline")}
description={t("empty_installed_apps_description")}
buttonRaw={
@@ -10,7 +10,8 @@ import findDurationType from "@calcom/lib/findDurationType";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PeriodType } from "@calcom/prisma/client";
import type { BookingLimit } from "@calcom/types/Calendar";
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
import { Button, DateRangePicker, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
import { FiPlus, FiTrash } from "@calcom/ui/components/icon";
const MinimumBookingNoticeInput = React.forwardRef<
HTMLInputElement,
@@ -419,19 +420,21 @@ const BookingLimits = () => {
defaultValue={BOOKING_LIMIT_OPTIONS.find((option) => option.value === key)}
onChange={(val) => {
const current = currentBookingLimits;
const currentValue = watchBookingLimits[bookingLimitKey];
// Removes limit from previous selected value (eg when changed from per_week to per_month, we unset per_week here)
delete current[bookingLimitKey];
const newData = {
...current,
// Set limit to new selected value (in the example above this means we set the limit to per_week here).
[val?.value as BookingLimitsKey]: watchBookingLimits[bookingLimitKey],
[val?.value as BookingLimitsKey]: currentValue,
};
onChange(newData);
}}
/>
<Button
variant="icon"
StartIcon={Icon.FiTrash}
StartIcon={FiTrash}
color="destructive"
onClick={() => {
const current = currentBookingLimits;
@@ -445,7 +448,7 @@ const BookingLimits = () => {
{currentBookingLimits && Object.keys(currentBookingLimits).length <= 3 && (
<Button
color="minimal"
StartIcon={Icon.FiPlus}
StartIcon={FiPlus}
onClick={() => {
if (!currentBookingLimits || !watchBookingLimits) return;
const currentKeys = Object.keys(watchBookingLimits);
@@ -458,7 +461,7 @@ const BookingLimits = () => {
setValue("bookingLimits", {
...watchBookingLimits,
[rest[0].value]: undefined,
[rest[0].value]: 1,
});
}}>
{t("add_limit")}
+42 -16
View File
@@ -9,12 +9,17 @@ import { Controller, useForm, useFormContext } from "react-hook-form";
import { MultiValue } from "react-select";
import { z } from "zod";
import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
import {
EventLocationType,
getEventLocationType,
MeetLocationType,
LocationType,
} from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { slugify } from "@lib/slugify";
import { slugify } from "@calcom/lib/slugify";
import { Button, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { FiEdit2, FiCheck, FiX, FiPlus } from "@calcom/ui/components/icon";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import LocationSelect, {
@@ -34,6 +39,16 @@ const getLocationFromType = (
}
};
const getDefaultLocationValue = (options: EventTypeSetupProps["locationOptions"], type: string) => {
for (const locationType of options) {
for (const location of locationType.options) {
if (location.value === type && location.disabled === false) {
return location;
}
}
}
};
export const EventSetupTab = (
props: Pick<EventTypeSetupProps, "eventType" | "locationOptions" | "team" | "teamMembers">
) => {
@@ -69,7 +84,12 @@ export const EventSetupTab = (
const removeLocation = (selectedLocation: typeof eventType.locations[number]) => {
formMethods.setValue(
"locations",
formMethods.getValues("locations").filter((location) => location.type !== selectedLocation.type),
formMethods.getValues("locations").filter((location) => {
if (location.type === LocationType.InPerson) {
return location.address !== selectedLocation.address;
}
return location.type !== selectedLocation.type;
}),
{ shouldValidate: true }
);
};
@@ -84,14 +104,14 @@ export const EventSetupTab = (
...details,
type: newLocationType,
};
} else {
copy[existingIdx] = {
...formMethods.getValues("locations")[existingIdx],
...details,
};
}
formMethods.setValue("locations", copy);
formMethods.setValue("locations", [
...copy,
...(newLocationType === LocationType.InPerson && editingLocationType === ""
? [{ ...details, type: newLocationType }]
: []),
]);
} else {
formMethods.setValue(
"locations",
@@ -138,15 +158,19 @@ export const EventSetupTab = (
return true;
});
const defaultValue = getDefaultLocationValue(locationOptions, "integrations:daily");
return (
<div className="w-full">
{validLocations.length === 0 && (
<div className="flex">
<LocationSelect
defaultValue={defaultValue}
placeholder={t("select")}
options={locationOptions}
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
menuPlacement="auto"
onChange={(e: SingleValueLocationOption) => {
if (e?.value) {
const newLocationType = e.value;
@@ -173,7 +197,9 @@ export const EventSetupTab = (
return null;
}
return (
<li key={location.type} className="mb-2 rounded-md border border-gray-300 py-1.5 px-2">
<li
key={`${location.type}${index}`}
className="mb-2 rounded-md border border-gray-300 py-1.5 px-2">
<div className="flex max-w-full justify-between">
<div key={index} className="flex flex-grow items-center">
<img
@@ -198,10 +224,10 @@ export const EventSetupTab = (
}}
aria-label={t("edit")}
className="mr-1 p-1 text-gray-500 hover:text-gray-900">
<Icon.FiEdit2 className="h-4 w-4" />
<FiEdit2 className="h-4 w-4" />
</button>
<button type="button" onClick={() => removeLocation(location)} aria-label={t("remove")}>
<Icon.FiX className="border-l-1 h-6 w-6 pl-1 text-gray-500 hover:text-gray-900 " />
<FiX className="border-l-1 h-6 w-6 pl-1 text-gray-500 hover:text-gray-900 " />
</button>
</div>
</div>
@@ -210,7 +236,7 @@ export const EventSetupTab = (
})}
{validLocations.some((location) => location.type === MeetLocationType) && (
<div className="flex text-sm text-gray-600">
<Icon.FiCheck className="mt-0.5 mr-1.5 h-2 w-2.5" />
<FiCheck className="mt-0.5 mr-1.5 h-2 w-2.5" />
<Trans i18nKey="event_type_requres_google_cal">
<p>
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
@@ -227,7 +253,7 @@ export const EventSetupTab = (
)}
{validLocations.length > 0 && validLocations.length !== locationOptions.length && (
<li>
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
<Button StartIcon={FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
{t("add_location")}
</Button>
</li>
@@ -1,4 +1,4 @@
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { EventTypeSetupProps } from "pages/event-types/[type]";
import { useState } from "react";
import { TbWebhook } from "react-icons/tb";
@@ -9,7 +9,8 @@ import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Webhook } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogContent, EmptyScreen, Icon, showToast } from "@calcom/ui";
import { Button, Dialog, DialogContent, EmptyScreen, showToast } from "@calcom/ui";
import { FiPlus } from "@calcom/ui/components/icon";
export const EventTeamWebhooksTab = ({
eventType,
@@ -84,7 +85,7 @@ export const EventTeamWebhooksTab = ({
<Button
color="secondary"
data-testid="new_webhook"
StartIcon={Icon.FiPlus}
StartIcon={FiPlus}
onClick={() => setCreateModalOpen(true)}>
{t("new_webhook")}
</Button>
@@ -130,7 +131,10 @@ export const EventTeamWebhooksTab = ({
{/* New webhook dialog */}
<Dialog open={createModalOpen} onOpenChange={(isOpen) => !isOpen && setCreateModalOpen(false)}>
<DialogContent title={t("create_webhook")} description={t("create_webhook_team_event_type")}>
<DialogContent
enableOverflow
title={t("create_webhook")}
description={t("create_webhook_team_event_type")}>
<WebhookForm
onSubmit={onCreateWebhook}
onCancel={() => setCreateModalOpen(false)}
@@ -23,7 +23,6 @@ import {
DropdownItem,
DropdownMenuTrigger,
HorizontalTabs,
Icon,
Label,
showToast,
Skeleton,
@@ -32,6 +31,21 @@ import {
VerticalDivider,
VerticalTabs,
} from "@calcom/ui";
import {
FiLink,
FiCalendar,
FiClock,
FiSliders,
FiRepeat,
FiGrid,
FiZap,
FiUsers,
FiExternalLink,
FiCode,
FiTrash,
FiMoreHorizontal,
FiLoader,
} from "@calcom/ui/components/icon";
import { EmbedButton, EmbedDialog } from "@components/Embed";
@@ -63,44 +77,44 @@ function getNavigation(props: {
{
name: "event_setup_tab_title",
href: `/event-types/${eventType.id}?tabName=setup`,
icon: Icon.FiLink,
icon: FiLink,
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
},
{
name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`,
icon: Icon.FiCalendar,
icon: FiCalendar,
info: `default_schedule_name`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",
href: `/event-types/${eventType.id}?tabName=limits`,
icon: Icon.FiClock,
icon: FiClock,
info: `event_limit_tab_description`,
},
{
name: "event_advanced_tab_title",
href: `/event-types/${eventType.id}?tabName=advanced`,
icon: Icon.FiSliders,
icon: FiSliders,
info: `event_advanced_tab_description`,
},
{
name: "recurring",
href: `/event-types/${eventType.id}?tabName=recurring`,
icon: Icon.FiRepeat,
icon: FiRepeat,
info: `recurring_event_tab_description`,
},
{
name: "apps",
href: `/event-types/${eventType.id}?tabName=apps`,
icon: Icon.FiGrid,
icon: FiGrid,
//TODO: Handle proper translation with count handling
info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`,
},
{
name: "workflows",
href: `/event-types/${eventType.id}?tabName=workflows`,
icon: Icon.FiZap,
icon: FiZap,
info: `${enabledWorkflowsNumber} ${t("active")}`,
},
];
@@ -157,7 +171,7 @@ function EventTypeSingleLayout({
navigation.splice(2, 0, {
name: "assignment",
href: `/event-types/${eventType.id}?tabName=team`,
icon: Icon.FiUsers,
icon: FiUsers,
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
});
navigation.push({
@@ -181,7 +195,6 @@ function EventTypeSingleLayout({
backPath="/event-types"
title={eventType.title + " | " + t("event_type")}
heading={eventType.title}
subtitle={eventType.description || ""}
CTA={
<div className="flex items-center justify-end">
<div className="hidden items-center rounded-md px-2 sm:flex sm:hover:bg-gray-100">
@@ -211,14 +224,14 @@ function EventTypeSingleLayout({
variant="icon"
href={permalink}
rel="noreferrer"
StartIcon={Icon.FiExternalLink}
StartIcon={FiExternalLink}
/>
</Tooltip>
<Button
color="secondary"
variant="icon"
StartIcon={Icon.FiLink}
StartIcon={FiLink}
tooltip={t("copy_link")}
onClick={() => {
navigator.clipboard.writeText(permalink);
@@ -227,7 +240,7 @@ function EventTypeSingleLayout({
/>
<EmbedButton
embedUrl={encodeURIComponent(embedLink)}
StartIcon={Icon.FiCode}
StartIcon={FiCode}
color="secondary"
variant="icon"
tooltip={t("embed")}
@@ -235,7 +248,7 @@ function EventTypeSingleLayout({
<Button
color="secondary"
variant="icon"
StartIcon={Icon.FiTrash}
StartIcon={FiTrash}
tooltip={t("delete")}
disabled={!hasPermsToDelete}
onClick={() => setDeleteDialogOpen(true)}
@@ -246,19 +259,14 @@ function EventTypeSingleLayout({
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
className="lg:hidden"
StartIcon={Icon.FiMoreHorizontal}
variant="icon"
color="secondary"
/>
<Button className="lg:hidden" StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem className="focus:ring-gray-100">
<DropdownItem
target="_blank"
type="button"
StartIcon={Icon.FiExternalLink}
StartIcon={FiExternalLink}
href={permalink}
rel="noreferrer">
{t("preview")}
@@ -267,7 +275,7 @@ function EventTypeSingleLayout({
<DropdownMenuItem className="focus:ring-gray-100">
<DropdownItem
type="button"
StartIcon={Icon.FiLink}
StartIcon={FiLink}
onClick={() => {
navigator.clipboard.writeText(permalink);
showToast("Link copied!", "success");
@@ -278,7 +286,7 @@ function EventTypeSingleLayout({
<DropdownMenuItem className="focus:ring-gray-100">
<DropdownItem
type="button"
StartIcon={Icon.FiTrash}
StartIcon={FiTrash}
disabled={!hasPermsToDelete}
onClick={() => setDeleteDialogOpen(true)}>
{t("delete")}
@@ -313,8 +321,8 @@ function EventTypeSingleLayout({
</Button>
</div>
}>
<Suspense fallback={<Icon.FiLoader />}>
<div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
<Suspense fallback={<FiLoader />}>
<div className="flex flex-col xl:flex-row xl:space-x-6">
<div className="hidden xl:block">
<VerticalTabs
className="primary-navigation"
@@ -1,4 +1,5 @@
import { Icon, SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { FiClock, FiUser } from "@calcom/ui/components/icon";
function SkeletonLoader() {
return (
@@ -31,11 +32,11 @@ function SkeletonItem() {
<div className="">
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
<li className="flex items-center whitespace-nowrap">
<Icon.FiClock className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<FiClock className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<SkeletonText className="h-4 w-12" />
</li>
<li className="flex items-center whitespace-nowrap">
<Icon.FiUser className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<FiUser className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<SkeletonText className="h-4 w-12" />
</li>
</ul>
@@ -1,5 +1,3 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { CalendarSwitch } from "./CalendarSwitch";
interface IConnectedCalendarItem {
@@ -21,7 +19,7 @@ interface IConnectedCalendarItem {
const ConnectedCalendarItem = (prop: IConnectedCalendarItem) => {
const { name, logo, externalId, calendars, integrationType } = prop;
const { t } = useLocale();
return (
<>
<div className="flex flex-row items-center p-4">
@@ -1,15 +1,19 @@
import { ArrowRightIcon } from "@heroicons/react/solid";
import MarkdownIt from "markdown-it";
import { useRouter } from "next/router";
import { FormEvent, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import turndown from "@calcom/lib/turndownService";
import { trpc } from "@calcom/trpc/react";
import { Button, ImageUploader, showToast, TextArea } from "@calcom/ui";
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
import { Avatar } from "@calcom/ui";
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
const md = new MarkdownIt("default", { html: true, breaks: true });
type FormData = {
bio: string;
};
@@ -21,13 +25,10 @@ const UserProfile = (props: IUserProfileProps) => {
const { user } = props;
const { t } = useLocale();
const avatarRef = useRef<HTMLInputElement>(null!);
const bioRef = useRef<HTMLTextAreaElement>(null);
const {
register,
setValue,
handleSubmit,
formState: { errors },
} = useForm<FormData>({ defaultValues: { bio: user?.bio || "" } });
const { setValue, handleSubmit, getValues } = useForm<FormData>({
defaultValues: { bio: user?.bio || "" },
});
const { data: eventTypes } = trpc.viewer.eventTypes.list.useQuery();
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
const utils = trpc.useContext();
@@ -138,25 +139,12 @@ const UserProfile = (props: IUserProfileProps) => {
</div>
</div>
<fieldset className="mt-8">
<label htmlFor="bio" className="mb-2 block text-sm font-medium text-gray-700">
{t("about")}
</label>
<TextArea
{...register("bio", { required: true })}
ref={bioRef}
name="bio"
id="bio"
className="mt-1 block h-[60px] w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm"
defaultValue={user?.bio || undefined}
onChange={(event) => {
setValue("bio", event.target.value);
}}
<Label className="mb-2 block text-sm font-medium text-gray-700">{t("about")}</Label>
<Editor
getText={() => md.render(getValues("bio") || user?.bio || "")}
setText={(value: string) => setValue("bio", turndown(value))}
excludedToolbarItems={["blockType"]}
/>
{errors.bio && (
<p data-testid="required" className="py-2 text-xs text-red-500">
{t("required")}
</p>
)}
<p className="mt-2 font-sans text-sm font-normal text-gray-600 dark:text-white">
{t("few_sentences_about_yourself")}
</p>
@@ -1,10 +1,10 @@
import { Icon } from "@calcom/ui";
import { ShieldCheckIcon } from "@calcom/ui/components/icon";
const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => {
return (
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<Icon.ShieldCheckIcon className="h-6 w-6 text-white" />
<ShieldCheckIcon className="h-6 w-6 text-white" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="font-cal text-lg font-medium leading-6 text-gray-900" id="modal-title">
@@ -2,7 +2,8 @@ import { useMutation } from "@tanstack/react-query";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Badge, Icon, showToast, Switch } from "@calcom/ui";
import { Badge, showToast, Switch } from "@calcom/ui";
import { FiArrowLeft } from "@calcom/ui/components/icon";
export function CalendarSwitch(props: {
type: string;
@@ -74,7 +75,7 @@ export function CalendarSwitch(props: {
/>
{props.defaultSelected && (
<Badge variant="gray">
<Icon.FiArrowLeft className="mr-1" /> {t("adding_events_to")}
<FiArrowLeft className="mr-1" /> {t("adding_events_to")}
</Badge>
)}
</div>
+18 -4
View File
@@ -1,3 +1,4 @@
import MarkdownIt from "markdown-it";
import Link from "next/link";
import { TeamPageProps } from "pages/team/[slug]";
@@ -6,6 +7,8 @@ import { Avatar } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
type TeamType = TeamPageProps["team"];
type MembersType = TeamType["members"];
type MemberType = MembersType[number];
@@ -13,6 +16,8 @@ type MemberType = MembersType[number];
const Member = ({ member, teamName }: { member: MemberType; teamName: string | null }) => {
const { t } = useLocale();
const isBioEmpty = !member.bio || !member.bio.replace("<p><br></p>", "").length;
return (
<Link key={member.id} href={`/${member.username}`}>
<div className="sm:min-w-80 sm:max-w-80 dark:bg-darkgray-200 dark:hover:bg-darkgray-300 group flex min-h-full w-[90%] flex-col space-y-2 rounded-md bg-white p-4 hover:cursor-pointer hover:bg-gray-50 ">
@@ -23,9 +28,18 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
/>
<section className="line-clamp-4 mt-2 w-full space-y-1">
<p className="font-medium text-gray-900 dark:text-white">{member.name}</p>
<p className="line-clamp-3 overflow-ellipsis text-sm font-normal text-gray-500 dark:text-white">
{member.bio || t("user_from_team", { user: member.name, team: teamName })}
</p>
<div className="line-clamp-3 overflow-ellipsis text-sm font-normal text-gray-500 dark:text-white">
{!isBioEmpty ? (
<>
<div
className="dark:text-darkgray-600 text-sm text-gray-500 [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: md.render(member.bio || "") }}
/>
</>
) : (
t("user_from_team", { user: member.name, team: teamName })
)}
</div>
</section>
</div>
</Link>
@@ -46,7 +60,7 @@ const Members = ({ members, teamName }: { members: MembersType; teamName: string
);
};
const Team = ({ team }: TeamPageProps) => {
const Team = ({ team }: Omit<TeamPageProps, "trpcState">) => {
return (
<div>
<Members members={team.members} teamName={team.name} />
+2 -5
View File
@@ -1,7 +1,7 @@
import classNames from "classnames";
import { APP_NAME, LOGO } from "@calcom/lib/constants";
import { Credits, HeadSeo } from "@calcom/ui";
import { HeadSeo } from "@calcom/ui";
import Loader from "@components/Loader";
@@ -35,10 +35,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
<div className="mx-2 rounded-md border border-gray-200 bg-white px-4 py-10 sm:px-10">
{props.children}
</div>
<div className="mt-8 text-center text-sm text-gray-600">
{props.footerText}
<Credits />
</div>
<div className="mt-8 text-center text-sm text-gray-600">{props.footerText}</div>
</div>
</div>
);
+3 -3
View File
@@ -1,8 +1,8 @@
import classNames from "classnames";
import React, { useState } from "react";
import { useState } from "react";
import { ControllerRenderProps } from "react-hook-form";
import { Icon } from "@calcom/ui";
import { FiEdit2 } from "@calcom/ui/components/icon";
const EditableHeading = function EditableHeading({
value,
@@ -21,7 +21,7 @@ const EditableHeading = function EditableHeading({
<label className="min-w-8 relative inline-block">
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value}&nbsp;</span>
{!isEditing && isReady && (
<Icon.FiEdit2 className="ml-1 inline h-4 w-4 align-top text-gray-700 group-hover:text-gray-500" />
<FiEdit2 className=" ml-1 -mt-px inline h-3 w-3 text-gray-500 group-hover:text-gray-500" />
)}
<input
{...passThroughProps}
+3 -2
View File
@@ -1,11 +1,12 @@
import { Icon, Tooltip } from "@calcom/ui";
import { Tooltip } from "@calcom/ui";
import { FiInfo } from "@calcom/ui/components/icon";
export default function InfoBadge({ content }: { content: string }) {
return (
<>
<Tooltip side="top" content={content}>
<span title={content}>
<Icon.FiInfo className="relative top-px left-1 right-1 mt-px h-4 w-4 text-gray-500" />
<FiInfo className="relative top-px left-1 right-1 mt-px h-4 w-4 text-gray-500" />
</span>
</Tooltip>
</>
@@ -11,7 +11,8 @@ import { User } from "@calcom/prisma/client";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button, Dialog, DialogClose, DialogContent, DialogHeader, Icon, Input, Label } from "@calcom/ui";
import { Button, Dialog, DialogClose, DialogContent, DialogHeader, Input, Label } from "@calcom/ui";
import { FiCheck, FiEdit2, FiExternalLink, StarIconSolid } from "@calcom/ui/components/icon";
export enum UsernameChangeStatusEnum {
UPGRADE = "UPGRADE",
@@ -230,8 +231,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
isInputUsernamePremium ? "text-orange-400" : "",
usernameIsAvailable ? "" : ""
)}>
{isInputUsernamePremium ? <Icon.StarIconSolid className="mt-[2px] w-6" /> : <></>}
{!isInputUsernamePremium && usernameIsAvailable ? <Icon.FiCheck className="mt-2 w-6" /> : <></>}
{isInputUsernamePremium ? <StarIconSolid className="mt-[2px] w-6" /> : <></>}
{!isInputUsernamePremium && usernameIsAvailable ? <FiCheck className="mt-2 w-6" /> : <></>}
</span>
</div>
</div>
@@ -249,7 +250,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<DialogContent>
<div className="flex flex-row">
<div className="xs:hidden flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<Icon.FiEdit2 className="m-auto h-6 w-6" />
<FiEdit2 className="m-auto h-6 w-6" />
</div>
<div className="mb-4 w-full px-4 pt-1">
<DialogHeader title={t("confirm_username_change_dialog_title")} />
@@ -283,7 +284,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
data-testid="go-to-billing"
href={paymentLink}>
<>
{t("go_to_stripe_billing")} <Icon.FiExternalLink className="ml-1 h-4 w-4" />
{t("go_to_stripe_billing")} <FiExternalLink className="ml-1 h-4 w-4" />
</>
</Button>
)}
@@ -7,17 +7,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogHeader,
Icon,
Input,
Label,
TextField,
} from "@calcom/ui";
import { Button, Dialog, DialogClose, DialogContent, DialogHeader, TextField } from "@calcom/ui";
import { FiCheck, FiEdit2 } from "@calcom/ui/components/icon";
interface ICustomUsernameProps {
currentUsername: string | undefined;
@@ -145,7 +136,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
{currentUsername !== inputUsernameValue && (
<div className="absolute right-[2px] top-6 flex flex-row">
<span className={classNames("mx-2 py-2")}>
{usernameIsAvailable ? <Icon.FiCheck className="w-6" /> : <></>}
{usernameIsAvailable ? <FiCheck className="w-6" /> : <></>}
</span>
</div>
)}
@@ -165,7 +156,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
<DialogContent>
<div style={{ display: "flex", flexDirection: "row" }}>
<div className="xs:hidden flex h-10 w-10 flex-shrink-0 justify-center rounded-full bg-[#FAFAFA]">
<Icon.FiEdit2 className="m-auto h-6 w-6" />
<FiEdit2 className="m-auto h-6 w-6" />
</div>
<div className="mb-4 w-full px-4 pt-1">
<DialogHeader title={t("confirm_username_change_dialog_title")} />
@@ -2,7 +2,8 @@ import React from "react";
import { Props } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Avatar } from "@calcom/ui";
import { Avatar } from "@calcom/ui";
import { FiX } from "@calcom/ui/components/icon";
import Select from "@components/ui/form/Select";
@@ -48,7 +49,7 @@ export const CheckedSelect = ({
alt={option.label}
/>
{option.label}
<Icon.FiX
<FiX
onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-gray-500"
/>
+2 -2
View File
@@ -3,7 +3,7 @@ import "react-calendar/dist/Calendar.css";
import "react-date-picker/dist/DatePicker.css";
import PrimitiveDatePicker from "react-date-picker/dist/entry.nostyle";
import { Icon } from "@calcom/ui";
import { FiCalendar } from "@calcom/ui/components/icon";
import classNames from "@lib/classNames";
@@ -23,7 +23,7 @@ export const DatePicker = ({ minDate, disabled, date, onDatesChange, className }
className
)}
clearIcon={null}
calendarIcon={<Icon.FiCalendar className="h-5 w-5 text-gray-500" />}
calendarIcon={<FiCalendar className="h-5 w-5 text-gray-500" />}
value={date}
minDate={minDate}
disabled={disabled}
-1
View File
@@ -1 +0,0 @@
export * from "@calcom/lib/availability";
-2
View File
@@ -1,2 +0,0 @@
// TODO: Remove this file once everything is imported from `@calcom/lib`
export * from "@calcom/lib/constants";
-1
View File
@@ -1 +0,0 @@
export * from "@calcom/lib/weekday";
-3
View File
@@ -1,3 +0,0 @@
/* Prefer import from `@calcom/lib/isOutOfBounds` */
export * from "@calcom/lib/isOutOfBounds";
export { default } from "@calcom/lib/isOutOfBounds";
-1
View File
@@ -1 +0,0 @@
export * from "@calcom/core/location";
-1
View File
@@ -1 +0,0 @@
export { default } from "@calcom/prisma";
-2
View File
@@ -1,2 +0,0 @@
// TODO: Remove this file once everything is imported from `@calcom/lib`
export * from "@calcom/lib/random";
-3
View File
@@ -1,3 +0,0 @@
/** Prefer import from `@calcom/lib/slots` */
export * from "@calcom/lib/slots";
export { default } from "@calcom/lib/slots";
-3
View File
@@ -1,3 +0,0 @@
// TODO: Remove this file once every `classNames` is imported from `@calcom/lib`
export * from "@calcom/lib/slugify";
export { default } from "@calcom/lib/slugify";
+14
View File
@@ -176,6 +176,20 @@ const nextConfig = {
}, */
];
},
async headers() {
return [
{
// prettier-ignore
source: "/:path*((?<!\/embed$)(?<!\/embed\/preview\.html$))",
headers: [
{
key: "X-Frame-Options",
value: "DENY",
},
],
},
];
},
async redirects() {
const redirects = [
{
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.5.3",
"version": "2.5.8",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -23,7 +23,7 @@
"yarn": ">=1.19.0 < 2.0.0"
},
"dependencies": {
"@boxyhq/saml-jackson": "1.3.6",
"@boxyhq/saml-jackson": "1.7.1",
"@calcom/app-store": "*",
"@calcom/app-store-cli": "*",
"@calcom/core": "*",
@@ -37,7 +37,7 @@
"@calcom/trpc": "*",
"@calcom/tsconfig": "*",
"@calcom/ui": "*",
"@daily-co/daily-js": "^0.26.0",
"@daily-co/daily-js": "^0.37.0",
"@formkit/auto-animate": "^1.0.0-beta.5",
"@glidejs/glide": "^3.5.2",
"@heroicons/react": "^1.0.6",
@@ -60,6 +60,7 @@
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@tanstack/react-query": "^4.3.9",
"@types/turndown": "^5.0.1",
"@vercel/edge-functions-ui": "^0.2.1",
"@vercel/og": "^0.0.21",
"accept-language-parser": "^1.5.0",
@@ -118,6 +119,7 @@
"stripe": "^9.16.0",
"superjson": "1.9.1",
"tailwindcss-radix": "^2.6.0",
"turndown": "^7.1.1",
"uuid": "^8.3.2",
"web3": "^1.7.5",
"zod": "^3.20.2"
@@ -162,6 +164,6 @@
"tailwindcss": "^3.2.1",
"ts-jest": "^28.0.8",
"ts-node": "^10.9.1",
"typescript": "^4.7.4"
"typescript": "^4.9.4"
}
}
+13 -12
View File
@@ -4,7 +4,8 @@ import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { COMPANY_NAME, DEVELOPER_DOCS, DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
import { Icon, HeadSeo } from "@calcom/ui";
import { HeadSeo } from "@calcom/ui";
import { FiFileText, FiCheck, FiBookOpen, FiChevronRight } from "@calcom/ui/components/icon";
import { useLocale } from "@lib/hooks/useLocale";
@@ -20,13 +21,13 @@ export default function Custom404() {
{
title: t("documentation"),
description: t("documentation_description"),
icon: Icon.FiFileText,
icon: FiFileText,
href: DOCS_URL,
},
{
title: t("blog"),
description: t("blog_description"),
icon: Icon.FiBookOpen,
icon: FiBookOpen,
href: `${WEBSITE_URL}/blog`,
},
];
@@ -75,7 +76,7 @@ export default function Custom404() {
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-green-50">
<Icon.FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" />
<FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
@@ -90,7 +91,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("the_infrastructure_plan")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
<FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</li>
@@ -103,7 +104,7 @@ export default function Custom404() {
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<Icon.FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" />
<FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
@@ -116,7 +117,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("prisma_studio_tip_description")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
<FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</Link>
</li>
@@ -161,7 +162,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("join_our_community")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
<FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</li>
@@ -218,7 +219,7 @@ export default function Custom404() {
rel="noreferrer">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-green-50">
<Icon.FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" />
<FiCheck className="h-6 w-6 text-green-500" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
@@ -233,7 +234,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("claim_username_and_schedule_events")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
<FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</li>
@@ -261,7 +262,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
<FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</Link>
</li>
@@ -307,7 +308,7 @@ export default function Custom404() {
<p className="text-base text-gray-500">{t("join_our_community")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
<FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</li>
+3 -2
View File
@@ -3,7 +3,8 @@ import { useRouter } from "next/router";
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, showToast } from "@calcom/ui";
import { Button, showToast } from "@calcom/ui";
import { FiCopy } from "@calcom/ui/components/icon";
export default function Error500() {
const { t } = useLocale();
@@ -33,7 +34,7 @@ export default function Error500() {
<Button
color="secondary"
className="mt-2 border-0 font-sans font-normal hover:bg-gray-300"
StartIcon={Icon.FiCopy}
StartIcon={FiCopy}
onClick={() => {
navigator.clipboard.writeText(router.query.error as string);
showToast("Link copied!", "success");
+20 -7
View File
@@ -1,4 +1,5 @@
import classNames from "classnames";
import MarkdownIt from "markdown-it";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -26,13 +27,16 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
import prisma from "@calcom/prisma";
import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Icon, HeadSeo, AvatarGroup, Avatar } from "@calcom/ui";
import { HeadSeo, AvatarGroup, Avatar } from "@calcom/ui";
import { BadgeCheckIcon, FiArrowRight } from "@calcom/ui/components/icon";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import { ssrInit } from "@server/lib/ssr";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props;
const [user] = users; //To be used when we only have a single user, not dynamic group
@@ -40,6 +44,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
const { t } = useLocale();
const router = useRouter();
const isBioEmpty = !user.bio || !user.bio.replace("<p><br></p>", "").length;
const groupEventTypes = props.users.some((user) => !user.allowDynamicBooking) ? (
<div className="space-y-6" data-testid="event-types">
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
@@ -54,8 +60,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
{eventTypes.map((type, index) => (
<li
key={index}
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
<Icon.FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
<FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
<Link
href={getUsernameSlugLink({ users: props.users, slug: type.slug })}
className="flex justify-between px-6 py-4"
@@ -134,10 +140,17 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
<h1 className="font-cal mb-1 text-3xl text-gray-900 dark:text-white">
{nameOrUsername}
{user.verified && (
<Icon.BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
<BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
)}
</h1>
<p className="dark:text-darkgray-600 text-s text-gray-500">{user.bio}</p>
{!isBioEmpty && (
<>
<div
className="dark:text-darkgray-600 text-sm text-gray-500 [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: md.render(user.bio || "") }}
/>
</>
)}
</div>
)}
<div
@@ -162,8 +175,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
<div
key={type.id}
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
<Icon.FiArrowRight className="absolute right-4 top-4 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
className="dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600">
<FiArrowRight className="absolute right-4 top-4 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
<Link
prefetch={false}
-1
View File
@@ -1,6 +1,5 @@
import MarkdownIt from "markdown-it";
import { GetStaticPaths, GetStaticPropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { z } from "zod";
import { privacyFilteredLocations, LocationObject } from "@calcom/app-store/locations";
-1
View File
@@ -1,5 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { LocationObject, privacyFilteredLocations } from "@calcom/app-store/locations";
import { parseRecurringEvent } from "@calcom/lib";
+35 -6
View File
@@ -2,6 +2,7 @@ import { IdentityProvider, UserPermissionRole } from "@prisma/client";
import { readFileSync } from "fs";
import Handlebars from "handlebars";
import NextAuth, { Session } from "next-auth";
import { encode } from "next-auth/jwt";
import { Provider } from "next-auth/providers";
import CredentialsProvider from "next-auth/providers/credentials";
import EmailProvider from "next-auth/providers/email";
@@ -17,14 +18,14 @@ import { ErrorCode, isPasswordValid, verifyPassword } from "@calcom/lib/auth";
import { APP_NAME, IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { defaultCookies } from "@calcom/lib/default-cookies";
import { randomString } from "@calcom/lib/random";
import rateLimit from "@calcom/lib/rateLimit";
import { serverConfig } from "@calcom/lib/serverConfig";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { teamMetadataSchema, userMetadata } from "@calcom/prisma/zod-utils";
import CalComAdapter from "@lib/auth/next-auth-custom-adapter";
import { randomString } from "@lib/random";
import slugify from "@lib/slugify";
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
@@ -60,6 +61,7 @@ const providers: Provider[] = [
username: true,
name: true,
email: true,
metadata: true,
identityProvider: true,
password: true,
twoFactorEnabled: true,
@@ -72,8 +74,9 @@ const providers: Provider[] = [
},
});
// Don't leak information about it being username or password that is invalid
if (!user) {
throw new Error(ErrorCode.UserNotFound);
throw new Error(ErrorCode.IncorrectUsernamePassword);
}
if (user.identityProvider !== IdentityProvider.CAL) {
@@ -81,12 +84,12 @@ const providers: Provider[] = [
}
if (!user.password) {
throw new Error(ErrorCode.UserMissingPassword);
throw new Error(ErrorCode.IncorrectUsernamePassword);
}
const isCorrectPassword = await verifyPassword(credentials.password, user.password);
if (!isCorrectPassword) {
throw new Error(ErrorCode.IncorrectPassword);
throw new Error(ErrorCode.IncorrectUsernamePassword);
}
if (user.twoFactorEnabled) {
@@ -233,7 +236,13 @@ if (true) {
})
);
}
function isNumber(n: string) {
return !isNaN(parseFloat(n)) && !isNaN(+n);
}
const calcomAdapter = CalComAdapter(prisma);
export default NextAuth({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
@@ -241,6 +250,26 @@ export default NextAuth({
session: {
strategy: "jwt",
},
jwt: {
// decorate the native JWT encode function
// Impl. detail: We don't pass through as this function is called with encode/decode functions.
encode: async ({ token, maxAge, secret }) => {
if (token?.sub && isNumber(token.sub)) {
const user = await prisma.user.findFirst({
where: { id: Number(token.sub) },
select: { metadata: true },
});
// if no user is found, we still don't want to crash here.
if (user) {
const metadata = userMetadata.parse(user.metadata);
if (metadata?.sessionTimeout) {
maxAge = metadata.sessionTimeout / 60;
}
}
}
return encode({ secret, token, maxAge });
},
},
cookies: defaultCookies(WEBAPP_URL?.startsWith("https://")),
pages: {
signIn: "/auth/login",
+1 -2
View File
@@ -4,10 +4,9 @@ import { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { sendPasswordResetEmail } from "@calcom/emails";
import { PASSWORD_RESET_EXPIRY_HOURS } from "@calcom/emails/templates/forgot-password-email";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma from "@calcom/prisma";
import { getTranslation } from "@server/lib/i18n";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const t = await getTranslation(req.body.language ?? "en", "common");
+37
View File
@@ -0,0 +1,37 @@
import { NextApiRequest, NextApiResponse } from "next";
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { HttpError } from "@lib/core/http/error";
// This is the callback endpoint for the OIDC provider
// A team must set this endpoint in the OIDC provider's configuration
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "GET") {
return res.status(400).send("Method not allowed");
}
const { code, state } = req.query as {
code: string;
state: string;
};
const { oauthController } = await jackson();
try {
const { redirect_url } = await oauthController.oidcAuthzResponse({ code, state });
if (!redirect_url) {
throw new HttpError({
message: "No redirect URL found",
statusCode: 500,
});
}
return res.redirect(302, redirect_url);
} catch (err) {
const { message, statusCode = 500 } = err as HttpError;
return res.status(statusCode).send(message);
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { IdentityProvider } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";
import { NextApiRequest } from "next";
import z from "zod";
import { isPasswordValid } from "@calcom/lib/auth";
@@ -50,7 +50,7 @@ async function handler(req: NextApiRequest) {
},
});
return { message: "First admin user created successfuly." };
return { message: "First admin user created successfully." };
}
export default defaultHandler({
+1 -2
View File
@@ -2,11 +2,10 @@ import { IdentityProvider } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";
import { hashPassword } from "@calcom/lib/auth";
import slugify from "@calcom/lib/slugify";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma from "@calcom/prisma";
import slugify from "@lib/slugify";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return;
+1 -2
View File
@@ -4,11 +4,10 @@ import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { sendOrganizerRequestReminderEmail } from "@calcom/emails";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { getTranslation } from "@server/lib/i18n";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.headers.authorization || req.query.apiKey;
if (process.env.CRON_API_KEY !== apiKey) {
@@ -19,6 +19,7 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
<Shell
isPublic
backPath="/apps"
smallHeading
heading={
<>
<Link
@@ -33,8 +34,7 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
</span>
)}
</>
}
large>
}>
<div className="mb-16">
<div className="grid-col-1 grid grid-cols-1 gap-3 md:grid-cols-3">
{apps.map((app) => {
+4 -3
View File
@@ -4,7 +4,8 @@ import Link from "next/link";
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, SkeletonText } from "@calcom/ui";
import { SkeletonText } from "@calcom/ui";
import { FiArrowLeft, FiArrowRight } from "@calcom/ui/components/icon";
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t, isLocaleReady } = useLocale();
@@ -15,7 +16,7 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
<Link
href="/apps"
className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
<Icon.FiArrowLeft className="h-4 w-4" />
<FiArrowLeft className="h-4 w-4" />
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
</Link>
</div>
@@ -31,7 +32,7 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
<h3 className="font-medium capitalize">{category.name}</h3>
<p className="text-sm text-gray-500">
{t("number_apps", { count: category.count })}{" "}
<Icon.FiArrowRight className="inline-block h-4 w-4" />
<FiArrowRight className="inline-block h-4 w-4" />
</p>
</div>
</Link>
+2 -2
View File
@@ -12,10 +12,10 @@ import {
AppStoreCategories,
HorizontalTabItemProps,
HorizontalTabs,
Icon,
TextField,
PopularAppsSlider,
} from "@calcom/ui";
import { FiSearch } from "@calcom/ui/components/icon";
import AppsLayout from "@components/apps/layouts/AppsLayout";
@@ -42,7 +42,7 @@ function AppsSearch({
return (
<TextField
className="!border-gray-100 bg-gray-100 !pl-0 focus:!ring-offset-0"
addOnLeading={<Icon.FiSearch className="h-4 w-4 text-gray-500" />}
addOnLeading={<FiSearch className="h-4 w-4 text-gray-500" />}
addOnClassname="!border-gray-100"
containerClassName={classNames("focus:!ring-offset-0", className)}
type="search"
+108 -59
View File
@@ -1,10 +1,11 @@
import { useRouter } from "next/router";
import { useReducer } from "react";
import z from "zod";
import { AppSettings } from "@calcom/app-store/_components/AppSettings";
import { InstallAppButton } from "@calcom/app-store/components";
import { InstalledAppVariants } from "@calcom/app-store/utils";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { App } from "@calcom/types/App";
@@ -13,16 +14,31 @@ import {
Alert,
Button,
EmptyScreen,
Icon,
List,
AppSkeletonLoader as SkeletonLoader,
ShellSubHeading,
DropdownMenuTrigger,
DropdownMenuContent,
Dropdown,
DropdownMenuItem,
DropdownItem,
} from "@calcom/ui";
import {
FiBarChart,
FiCalendar,
FiCreditCard,
FiGrid,
FiMoreHorizontal,
FiPlus,
FiShare2,
FiTrash,
FiVideo,
} from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
import AppListCard from "@components/AppListCard";
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
import IntegrationListItem from "@components/apps/IntegrationListItem";
import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout";
function ConnectOrDisconnectIntegrationButton(props: {
@@ -31,8 +47,9 @@ function ConnectOrDisconnectIntegrationButton(props: {
isGlobal?: boolean;
installed?: boolean;
invalidCredentialIds?: number[];
handleDisconnect: (credentialId: number) => void;
}) {
const { type, credentialIds, isGlobal, installed } = props;
const { type, credentialIds, isGlobal, installed, handleDisconnect } = props;
const { t } = useLocale();
const [credentialId] = credentialIds;
@@ -41,25 +58,24 @@ function ConnectOrDisconnectIntegrationButton(props: {
utils.viewer.integrations.invalidate();
};
if (credentialId) {
if (type === "stripe_payment") {
return (
<DisconnectIntegration
credentialId={credentialId}
trashIcon
onSuccess={handleOpenChange}
buttonProps={{ className: "border border-gray-300" }}
/>
);
}
if (credentialId || type === "stripe_payment" || isGlobal) {
return (
<DisconnectIntegration
credentialId={credentialId}
trashIcon
onSuccess={handleOpenChange}
buttonProps={{ className: "border border-gray-300" }}
/>
<Dropdown modal={false}>
<DropdownMenuTrigger asChild>
<Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<DropdownItem
color="destructive"
onClick={() => handleDisconnect(credentialId)}
disabled={isGlobal}
StartIcon={FiTrash}>
{t("remove_app")}
</DropdownItem>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
);
}
@@ -70,14 +86,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
</div>
);
}
/** We don't need to "Connect", just show that it's installed */
if (isGlobal) {
return (
<div className="truncate px-3 py-2">
<h3 className="text-sm font-medium text-gray-700">{t("default")}</h3>
</div>
);
}
return (
<InstallAppButton
type={type}
@@ -94,58 +103,64 @@ function ConnectOrDisconnectIntegrationButton(props: {
interface IntegrationsContainerProps {
variant?: typeof InstalledAppVariants[number];
exclude?: typeof InstalledAppVariants[number][];
handleDisconnect: (credentialId: number) => void;
}
interface IntegrationsListProps {
variant?: IntegrationsContainerProps["variant"];
data: RouterOutputs["viewer"]["integrations"];
handleDisconnect: (credentialId: number) => void;
}
const IntegrationsList = ({ data }: IntegrationsListProps) => {
const IntegrationsList = ({ data, handleDisconnect }: IntegrationsListProps) => {
return (
<List className="flex flex-col gap-6" noBorderTreatment>
<List>
{data.items
.filter((item) => item.invalidCredentialIds)
.map((item) => (
<IntegrationListItem
name={item.name}
slug={item.slug}
key={item.title}
title={item.title}
logo={item.logo}
<AppListCard
key={item.name}
description={item.description}
separate={true}
isTemplate={item.isTemplate}
title={item.name}
logo={item.logo}
isDefault={item.isGlobal}
shouldHighlight
slug={item.slug}
invalidCredential={item.invalidCredentialIds.length > 0}
actions={
<div className="flex w-16 justify-end">
<div className="flex justify-end">
<ConnectOrDisconnectIntegrationButton
credentialIds={item.credentialIds}
type={item.type}
isGlobal={item.isGlobal}
installed
invalidCredentialIds={item.invalidCredentialIds}
handleDisconnect={handleDisconnect}
/>
</div>
}>
<AppSettings slug={item.slug} />
</IntegrationListItem>
</AppListCard>
))}
</List>
);
};
const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps): JSX.Element => {
const IntegrationsContainer = ({
variant,
exclude,
handleDisconnect,
}: IntegrationsContainerProps): JSX.Element => {
const { t } = useLocale();
const query = trpc.viewer.integrations.useQuery({ variant, exclude, onlyInstalled: true });
const emptyIcon = {
calendar: Icon.FiCalendar,
conferencing: Icon.FiVideo,
automation: Icon.FiShare2,
analytics: Icon.FiBarChart,
payment: Icon.FiCreditCard,
web3: Icon.FiBarChart,
other: Icon.FiGrid,
calendar: FiCalendar,
conferencing: FiVideo,
automation: FiShare2,
analytics: FiBarChart,
payment: FiCreditCard,
web3: FiBarChart,
other: FiGrid,
};
return (
@@ -169,12 +184,12 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
: "/apps"
}
color="secondary"
StartIcon={Icon.FiPlus}>
StartIcon={FiPlus}>
{t("add")}
</Button>
}
/>
<IntegrationsList data={data} variant={variant} />
<IntegrationsList handleDisconnect={handleDisconnect} data={data} variant={variant} />
</div>
) : (
<EmptyScreen
@@ -206,6 +221,11 @@ const querySchema = z.object({
type querySchemaType = z.infer<typeof querySchema>;
type ModalState = {
isOpen: boolean;
credentialId: null | number;
};
export default function InstalledApps() {
const { t } = useLocale();
const router = useRouter();
@@ -218,14 +238,43 @@ export default function InstalledApps() {
"web3",
];
const [data, updateData] = useReducer(
(data: ModalState, partialData: Partial<ModalState>) => ({ ...data, ...partialData }),
{
isOpen: false,
credentialId: null,
}
);
const handleModelClose = () => {
updateData({ isOpen: false, credentialId: null });
};
const handleDisconnect = (credentialId: number) => {
updateData({ isOpen: true, credentialId });
};
return (
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
{categoryList.includes(category) && <IntegrationsContainer variant={category} />}
{category === "calendar" && <CalendarListContainer />}
{category === "other" && (
<IntegrationsContainer variant={category} exclude={[...categoryList, "calendar"]} />
)}
</InstalledAppsLayout>
<>
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
{categoryList.includes(category) && (
<IntegrationsContainer handleDisconnect={handleDisconnect} variant={category} />
)}
{category === "calendar" && <CalendarListContainer />}
{category === "other" && (
<IntegrationsContainer
handleDisconnect={handleDisconnect}
variant={category}
exclude={[...categoryList, "calendar"]}
/>
)}
</InstalledAppsLayout>
<DisconnectIntegrationModal
handleModelClose={handleModelClose}
isOpen={data.isOpen}
credentialId={data.credentialId}
/>
</>
);
}
+3 -2
View File
@@ -4,7 +4,8 @@ import { useRouter } from "next/router";
import z from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, SkeletonText } from "@calcom/ui";
import { Button, SkeletonText } from "@calcom/ui";
import { FiX } from "@calcom/ui/components/icon";
import AuthContainer from "@components/ui/AuthContainer";
@@ -28,7 +29,7 @@ export default function Error() {
<AuthContainer title="" description="">
<div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" />
<FiX className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
+8 -7
View File
@@ -9,14 +9,15 @@ import { FaGoogle } from "react-icons/fa";
import { SAMLLogin } from "@calcom/features/auth/SAMLLogin";
import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
import { ErrorCode, getSession } from "@calcom/lib/auth";
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { Alert, Button, EmailField, Icon, PasswordField } from "@calcom/ui";
import { Alert, Button, EmailField, PasswordField } from "@calcom/ui";
import { FiArrowLeft } from "@calcom/ui/components/icon";
import { ErrorCode, getSession } from "@lib/auth";
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import AddToHomescreen from "@components/AddToHomescreen";
@@ -51,8 +52,8 @@ export default function Login({
const errorMessages: { [key: string]: string } = {
// [ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
[ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`,
[ErrorCode.UserNotFound]: t("no_account_exists"),
// Don't leak information about whether an email is registered or not
[ErrorCode.IncorrectUsernamePassword]: t("incorrect_username_password"),
[ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`,
[ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`,
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
@@ -85,7 +86,7 @@ export default function Login({
setTwoFactorRequired(false);
methods.setValue("totpCode", "");
}}
StartIcon={Icon.FiArrowLeft}
StartIcon={FiArrowLeft}
color="minimal">
{t("go_back")}
</Button>
@@ -148,7 +149,7 @@ export default function Login({
</div>
<PasswordField
id="password"
autoComplete="current-password"
autoComplete="off"
required
className="mb-0"
{...register("password")}
+3 -2
View File
@@ -5,7 +5,8 @@ import { useEffect } from "react";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon } from "@calcom/ui";
import { Button } from "@calcom/ui";
import { FiCheck } from "@calcom/ui/components/icon";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -31,7 +32,7 @@ export default function Logout(props: Props) {
<AuthContainer title={t("logged_out")} description={t("youve_been_logged_out")} showLogo>
<div className="mb-4">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<Icon.FiCheck className="h-6 w-6 text-green-600" />
<FiCheck className="h-6 w-6 text-green-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
+2 -2
View File
@@ -1,7 +1,7 @@
import { useRouter } from "next/router";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { FiCheck } from "@calcom/ui/components/icon";
const StepDone = () => {
const router = useRouter();
@@ -18,7 +18,7 @@ const StepDone = () => {
}}>
<div className="min-h-36 my-6 flex flex-col items-center justify-center">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
<Icon.FiCheck className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
<FiCheck className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
+27 -23
View File
@@ -15,7 +15,6 @@ import type { Schedule as ScheduleType, TimeRange, WorkingHours } from "@calcom/
import {
Button,
Form,
Icon,
Label,
showToast,
Skeleton,
@@ -25,6 +24,7 @@ import {
Tooltip,
VerticalDivider,
} from "@calcom/ui";
import { FiInfo, FiPlus } from "@calcom/ui/components/icon";
import { HttpError } from "@lib/core/http/error";
@@ -51,17 +51,17 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
});
const { t } = useLocale();
return (
<div className="px-4 py-5 sm:p-6">
<div className="p-6">
<h3 className="font-medium leading-6 text-gray-900">
{t("date_overrides")}{" "}
<Tooltip content={t("date_overrides_info")}>
<span className="inline-block">
<Icon.FiInfo />
<FiInfo />
</span>
</Tooltip>
</h3>
<p className="mb-4 text-sm text-gray-500 ltr:mr-4 rtl:ml-4">{t("date_overrides_subtitle")}</p>
<div className="mt-1 space-y-2">
<p className="mb-4 text-sm text-gray-500">{t("date_overrides_subtitle")}</p>
<div className="space-y-2">
<DateOverrideList
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
remove={remove}
@@ -74,7 +74,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
onChange={(ranges) => append({ ranges })}
Trigger={
<Button color="secondary" StartIcon={Icon.FiPlus} data-testid="add-override">
<Button color="secondary" StartIcon={FiPlus} data-testid="add-override">
Add an override
</Button>
}
@@ -185,25 +185,29 @@ export default function Availability({ schedule }: { schedule: number }) {
...values,
});
}}
className="flex flex-col pb-16 sm:mx-0 xl:flex-row xl:space-x-6">
<div className="flex-1 divide-y divide-gray-200 rounded-md border">
<div className=" py-5 sm:p-6">
{typeof me.data?.weekStart === "string" && (
<Schedule
control={control}
name="schedule"
weekStart={
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
me.data?.weekStart
) as 0 | 1 | 2 | 3 | 4 | 5 | 6
}
/>
)}
className="flex flex-col sm:mx-0 xl:flex-row xl:space-x-6">
<div className="flex-1 flex-row xl:mr-0">
<div className="mb-6 rounded-md border">
<div>
{typeof me.data?.weekStart === "string" && (
<Schedule
control={control}
name="schedule"
weekStart={
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"].indexOf(
me.data?.weekStart
) as 0 | 1 | 2 | 3 | 4 | 5 | 6
}
/>
)}
</div>
</div>
<div className="my-6 rounded-md border">
{data?.workingHours && <DateOverride workingHours={data.workingHours} />}
</div>
{data?.workingHours && <DateOverride workingHours={data.workingHours} />}
</div>
<div className="min-w-40 col-span-3 space-y-2 lg:col-span-1">
<div className="xl:max-w-80 mt-4 w-full pr-4 sm:p-0">
<div className="xl:max-w-80 w-full pr-4 sm:ml-0 sm:mr-36 sm:p-0">
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
@@ -223,7 +227,7 @@ export default function Availability({ schedule }: { schedule: number }) {
}
/>
</div>
<hr className="my-8" />
<hr className="my-6 mr-8" />
<div className="rounded-md">
<h3 className="text-sm font-medium text-gray-900">{t("something_doesnt_look_right")}</h3>
<div className="mt-3 flex">
+3 -2
View File
@@ -5,7 +5,8 @@ import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules"
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { EmptyScreen, Icon, showToast } from "@calcom/ui";
import { EmptyScreen, showToast } from "@calcom/ui";
import { FiClock } from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
@@ -76,7 +77,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
{schedules.length === 0 ? (
<div className="flex justify-center">
<EmptyScreen
Icon={Icon.FiClock}
Icon={FiClock}
headline={t("new_schedule_heading")}
description={t("new_schedule_description")}
buttonRaw={<NewScheduleButton />}
+11 -8
View File
@@ -35,7 +35,8 @@ import { localStorage } from "@calcom/lib/webstorage";
import prisma from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Button, EmailInput, Icon, HeadSeo } from "@calcom/ui";
import { Button, EmailInput, HeadSeo } from "@calcom/ui";
import { FiX, FiChevronLeft, FiCheck, FiCalendar } from "@calcom/ui/components/icon";
import { timeZone } from "@lib/clock";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -142,7 +143,7 @@ function RedirectionToast({ url }: { url: string }) {
setIsToastVisible(false);
}}
className="-mr-1 flex rounded-md p-2 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-white">
<Icon.FiX className="h-6 w-6 text-white" />
<FiX className="h-6 w-6 text-white" />
</button>
</div>
</div>
@@ -168,6 +169,7 @@ const querySchema = z.object({
reschedule: stringToBoolean,
isSuccessBookingPage: z.string().optional(),
formerTime: z.string().optional(),
email: z.string().optional(),
});
export default function Success(props: SuccessProps) {
@@ -179,6 +181,7 @@ export default function Success(props: SuccessProps) {
cancel: isCancellationMode,
changes,
formerTime,
email,
} = querySchema.parse(router.query);
if ((isCancellationMode || changes) && typeof window !== "undefined") {
@@ -195,7 +198,6 @@ export default function Success(props: SuccessProps) {
console.error(`No location found `);
}
const email = props.bookingInfo?.user?.email;
const status = props.bookingInfo?.status;
const reschedule = props.bookingInfo.status === BookingStatus.ACCEPTED;
const cancellationReason = props.bookingInfo.cancellationReason;
@@ -254,6 +256,7 @@ export default function Success(props: SuccessProps) {
if (!sdkActionManager) return;
// TODO: We should probably make it consistent with Webhook payload. Some data is not available here, as and when requirement comes we can add
sdkActionManager.fire("bookingSuccessful", {
booking: bookingInfo,
eventType,
date: date.toString(),
duration: calculatedDuration,
@@ -356,7 +359,7 @@ export default function Success(props: SuccessProps) {
<Link
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-transparent dark:hover:text-white">
<Icon.FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
<FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
</Link>
</div>
)}
@@ -401,10 +404,10 @@ export default function Success(props: SuccessProps) {
<img src={giphyImage} alt="Gif from Giphy" />
)}
{!giphyImage && !needsConfirmation && !isCancelled && (
<Icon.FiCheck className="h-5 w-5 text-green-600" />
<FiCheck className="h-5 w-5 text-green-600" />
)}
{needsConfirmation && !isCancelled && <Icon.FiCalendar className="h-5 w-5 text-gray-900" />}
{isCancelled && <Icon.FiX className="h-5 w-5 text-red-600" />}
{needsConfirmation && !isCancelled && <FiCalendar className="h-5 w-5 text-gray-900" />}
{isCancelled && <FiX className="h-5 w-5 text-red-600" />}
</div>
<div className="mt-6 mb-8 text-center last:mb-0">
<h3
@@ -733,7 +736,7 @@ export default function Success(props: SuccessProps) {
<EmailInput
name="email"
id="email"
defaultValue={email || ""}
defaultValue={email}
className="mr- focus:border-brand border-bookinglightest dark:border-darkgray-300 mt-0 block w-full rounded-none rounded-l-md border-gray-300 shadow-sm focus:ring-black dark:bg-black dark:text-white sm:text-sm"
placeholder="rick.astley@cal.com"
/>
+3 -2
View File
@@ -9,7 +9,8 @@ import BookingLayout from "@calcom/features/bookings/layout/BookingLayout";
import { filterQuerySchema, useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Alert, Button, EmptyScreen, Icon } from "@calcom/ui";
import { Alert, Button, EmptyScreen } from "@calcom/ui";
import { FiCalendar } from "@calcom/ui/components/icon";
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
@@ -176,7 +177,7 @@ export default function Bookings() {
{query.status === "success" && isEmpty && (
<div className="flex items-center justify-center pt-2 xl:pt-0">
<EmptyScreen
Icon={Icon.FiCalendar}
Icon={FiCalendar}
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
description={t("no_status_bookings_yet_description", {
status: t(status).toLowerCase(),
+1 -1
View File
@@ -3,11 +3,11 @@ import { z } from "zod";
import { privacyFilteredLocations, LocationObject } from "@calcom/core/location";
import { parseRecurringEvent } from "@calcom/lib";
import { getWorkingHours } from "@calcom/lib/availability";
import { availiblityPageEventTypeSelect } from "@calcom/prisma";
import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { getWorkingHours } from "@lib/availability";
import { GetBookingType } from "@lib/getBooking";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
-1
View File
@@ -1,5 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { parseRecurringEvent } from "@calcom/lib";
import prisma from "@calcom/prisma";
@@ -10,8 +10,6 @@ import { z } from "zod";
import { EventLocationType } from "@calcom/core/location";
import { validateBookingLimitOrder } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
import getEventTypeById from "@calcom/lib/getEventTypeById";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
+125 -59
View File
@@ -2,7 +2,8 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { Fragment, useEffect, useState } from "react";
import { FC, useEffect, useState, memo } from "react";
import { z } from "zod";
import {
CreateEventTypeButton,
@@ -11,8 +12,12 @@ import {
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";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { RouterOutputs, trpc, TRPCClientError } from "@calcom/trpc/react";
import {
Avatar,
AvatarGroup,
Badge,
Button,
ButtonGroup,
@@ -26,13 +31,26 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
EmptyScreen,
Icon,
showToast,
Switch,
Avatar,
AvatarGroup,
Tooltip,
HorizontalTabs,
} from "@calcom/ui";
import {
FiArrowDown,
FiArrowUp,
FiClipboard,
FiCode,
FiCopy,
FiEdit,
FiEdit2,
FiExternalLink,
FiLink,
FiMoreHorizontal,
FiTrash,
FiUpload,
FiUsers,
} from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
@@ -60,6 +78,40 @@ interface EventTypeListProps {
types: EventType[];
}
interface MobileTeamsTabProps {
eventTypeGroups: EventTypeGroups;
}
const querySchema = z.object({
teamId: z.nullable(z.coerce.number()).optional().default(null),
});
const MobileTeamsTab: FC<MobileTeamsTabProps> = (props) => {
const { eventTypeGroups } = props;
const tabs = eventTypeGroups.map((item) => ({
name: item.profile.name ?? "",
href: item.teamId ? `/event-types?teamId=${item.teamId}` : "/event-types",
avatar: item.profile.image ?? `${WEBAPP_URL}/${item.profile.slug}/avatar.png`,
}));
const { data } = useTypedQuery(querySchema);
const events = eventTypeGroups.filter((item) => item.teamId === data.teamId);
return (
<div>
<HorizontalTabs tabs={tabs} />
{events.length && (
<EventTypeList
types={events[0].eventTypes}
group={events[0]}
groupIndex={0}
readOnly={events[0].metadata.readOnly}
/>
)}
</div>
);
};
const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGroup; readOnly: boolean }) => {
const { t } = useLocale();
@@ -83,12 +135,15 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
</span>
)}
</div>
<EventTypeDescription eventType={type} />
<EventTypeDescription
// @ts-expect-error FIXME We have a type mismtach here @hariombalhara @sean-brydon
eventType={type}
/>
</Link>
);
};
const MemoizedItem = React.memo(Item);
const MemoizedItem = memo(Item);
export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeListProps): JSX.Element => {
const { t } = useLocale();
@@ -178,7 +233,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
}
// inject selection data into url for correct router history
const openDuplicateModal = (eventType: EventType) => {
const openDuplicateModal = (eventType: EventType, group: EventTypeGroup) => {
const query = {
...router.query,
dialog: "duplicate-event-type",
@@ -187,6 +242,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
slug: eventType.slug,
id: eventType.id,
length: eventType.length,
pageSlug: group.profile.slug,
};
router.push(
@@ -263,7 +319,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<button
className="invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, -1)}>
<Icon.FiArrowUp className="h-5 w-5" />
<FiArrowUp className="h-5 w-5" />
</button>
)}
@@ -271,7 +327,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<button
className="invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow disabled:hover:border-inherit disabled:hover:text-gray-400 disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
onClick={() => moveEventType(index, 1)}>
<Icon.FiArrowDown className="h-5 w-5" />
<FiArrowDown className="h-5 w-5" />
</button>
)}
<MemoizedItem type={type} group={group} readOnly={readOnly} />
@@ -314,7 +370,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
target="_blank"
variant="icon"
href={calLink}
StartIcon={Icon.FiExternalLink}
StartIcon={FiExternalLink}
/>
</Tooltip>
@@ -322,7 +378,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<Button
color="secondary"
variant="icon"
StartIcon={Icon.FiLink}
StartIcon={FiLink}
onClick={() => {
showToast(t("link_copied"), "success");
navigator.clipboard.writeText(calLink);
@@ -338,7 +394,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
type="button"
variant="icon"
color="secondary"
StartIcon={Icon.FiMoreHorizontal}
StartIcon={FiMoreHorizontal}
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
@@ -346,7 +402,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownItem
type="button"
data-testid={"event-type-edit-" + type.id}
StartIcon={Icon.FiEdit2}
StartIcon={FiEdit2}
onClick={() => router.push("/event-types/" + type.id)}>
{t("edit")}
</DropdownItem>
@@ -355,8 +411,8 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownItem
type="button"
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}>
StartIcon={FiCopy}
onClick={() => openDuplicateModal(type, group)}>
{t("duplicate")}
</DropdownItem>
</DropdownMenuItem>
@@ -364,7 +420,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<EmbedButton
as={DropdownItem}
type="button"
StartIcon={Icon.FiCode}
StartIcon={FiCode}
className="w-full rounded-none"
embedUrl={encodeURIComponent(embedLink)}>
{t("embed")}
@@ -380,7 +436,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
setDeleteDialogOpen(true);
setDeleteDialogTypeId(type.id);
}}
StartIcon={Icon.FiTrash}
StartIcon={FiTrash}
className="w-full rounded-none">
{t("delete")}
</DropdownItem>
@@ -396,12 +452,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<div className="min-w-9 mx-5 flex sm:hidden">
<Dropdown>
<DropdownMenuTrigger asChild data-testid={"event-type-options-" + type.id}>
<Button
type="button"
variant="icon"
color="secondary"
StartIcon={Icon.FiMoreHorizontal}
/>
<Button type="button" variant="icon" color="secondary" StartIcon={FiMoreHorizontal} />
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
@@ -409,7 +460,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<Link href={calLink} target="_blank">
<Button
color="minimal"
StartIcon={Icon.FiExternalLink}
StartIcon={FiExternalLink}
className="w-full rounded-none">
{t("preview")}
</Button>
@@ -421,7 +472,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal"
className="w-full rounded-none text-left"
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiClipboard}
StartIcon={FiClipboard}
onClick={() => {
navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success");
@@ -436,7 +487,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal"
className="w-full rounded-none"
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiUpload}
StartIcon={FiUpload}
onClick={() => {
navigator
.share({
@@ -457,7 +508,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
onClick={() => router.push("/event-types/" + type.id)}
color="minimal"
className="w-full rounded-none"
StartIcon={Icon.FiEdit}>
StartIcon={FiEdit}>
{t("edit")}
</Button>
</DropdownMenuItem>
@@ -467,8 +518,8 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal"
className="w-full rounded-none"
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}>
StartIcon={FiCopy}
onClick={() => openDuplicateModal(type, group)}>
{t("duplicate")}
</Button>
</DropdownMenuItem>
@@ -480,7 +531,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
setDeleteDialogTypeId(type.id);
}}
color="destructive"
StartIcon={Icon.FiTrash}
StartIcon={FiTrash}
className="w-full rounded-none">
{t("delete")}
</Button>
@@ -518,14 +569,13 @@ const EventTypeListHeading = ({
}: EventTypeListHeadingProps): JSX.Element => {
return (
<div className="mb-4 flex items-center space-x-2">
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<Avatar
alt={profile?.name || ""}
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size="sm"
className="mt-1 inline ltr:mr-2 rtl:ml-2"
/>
</Link>
<Avatar
alt={profile?.name || ""}
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size="md"
className="mt-1 inline-flex justify-center"
/>
<div>
<Link
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
@@ -536,7 +586,7 @@ const EventTypeListHeading = ({
<span className="relative -top-px text-xs text-gray-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
<Link href={`/settings/teams/${teamId}/members`}>
<Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
<FiUsers className="mr-1 -mt-px inline h-3 w-3" />
{membershipCount}
</Badge>
</Link>
@@ -557,7 +607,7 @@ const CreateFirstEventTypeView = () => {
return (
<EmptyScreen
Icon={Icon.FiLink}
Icon={FiLink}
headline={t("new_event_type_heading")}
description={t("new_event_type_description")}
/>
@@ -577,6 +627,8 @@ const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer);
const EventTypesPage = () => {
const { t } = useLocale();
const isMobile = useMediaQuery("(max-width: 768px)");
return (
<div>
<Shell heading={t("event_types_page_title")} subtitle={t("event_types_page_subtitle")} CTA={<CTA />}>
@@ -584,26 +636,40 @@ const EventTypesPage = () => {
customLoader={<SkeletonLoader />}
success={({ data }) => (
<>
{data.eventTypeGroups.map((group, index) => (
<Fragment key={group.profile.slug}>
{/* hide list heading when there is only one (current user) */}
{(data.eventTypeGroups.length !== 1 || group.teamId) && (
<EventTypeListHeading
profile={group.profile}
membershipCount={group.metadata.membershipCount}
teamId={group.teamId}
/>
)}
<EventTypeList
types={group.eventTypes}
group={group}
groupIndex={index}
readOnly={group.metadata.readOnly}
/>
</Fragment>
))}
{data.eventTypeGroups.length > 1 ? (
<>
{isMobile ? (
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
) : (
data.eventTypeGroups.map((group, index) => (
<div className="flex flex-col" key={group.profile.slug}>
<EventTypeListHeading
profile={group.profile}
membershipCount={group.metadata.membershipCount}
teamId={group.teamId}
/>
<EventTypeList
types={group.eventTypes}
group={group}
groupIndex={index}
readOnly={group.metadata.readOnly}
/>
</div>
))
)}
</>
) : data.eventTypeGroups.length === 1 ? (
<EventTypeList
types={data.eventTypeGroups[0].eventTypes}
group={data.eventTypeGroups[0]}
groupIndex={0}
readOnly={data.eventTypeGroups[0].metadata.readOnly}
/>
) : (
<CreateFirstEventTypeView />
)}
{data.eventTypeGroups.length === 0 && <CreateFirstEventTypeView />}
<EmbedDialog />
</>
)}
@@ -7,10 +7,9 @@ import { z } from "zod";
import { getSession } from "@calcom/lib/auth";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import prisma from "@calcom/prisma";
import { Button, StepCard, Steps } from "@calcom/ui";
import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars";
@@ -60,7 +59,6 @@ const OnboardingPage = (props: IOnboardingPageProps) => {
`${t("set_availability_getting_started_subtitle_1")}`,
`${t("set_availability_getting_started_subtitle_2")}`,
],
skipText: `${t("set_my_availability_later")}`,
},
{
title: `${t("nearly_there")}`,
-32
View File
@@ -1,32 +0,0 @@
import { GetServerSidePropsContext } from "next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Meta } from "@calcom/ui";
import { getLayout } from "@components/auth/layouts/AdminLayout";
import { ssrInit } from "@server/lib/ssr";
function AdminUsersView() {
const { t } = useLocale();
return (
<>
<Meta title={t("users")} description={t("users_description")} />
<h1>{t("users_listing")}</h1>
</>
);
}
AdminUsersView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminUsersView;
+3 -4
View File
@@ -7,8 +7,8 @@ import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { classNames } from "@calcom/lib";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, Icon, Meta } from "@calcom/ui";
import { Button, Meta } from "@calcom/ui";
import { FiExternalLink } from "@calcom/ui/components/icon";
import { ssrInit } from "@server/lib/ssr";
@@ -36,7 +36,6 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
const BillingView = () => {
const { t } = useLocale();
const { data: user } = trpc.viewer.me.useQuery();
const [, loadChat] = useChat();
const [showChat, setShowChat] = useState(false);
const router = useRouter();
@@ -55,7 +54,7 @@ const BillingView = () => {
<CtaRow
title={t("billing_manage_details_title")}
description={t("billing_manage_details_description")}>
<Button color="primary" href={billingHref} target="_blank" EndIcon={Icon.FiExternalLink}>
<Button color="primary" href={billingHref} target="_blank" EndIcon={FiExternalLink}>
{t("billing_portal")}
</Button>
</CtaRow>
@@ -14,10 +14,10 @@ import {
Dialog,
DialogContent,
EmptyScreen,
Icon,
Meta,
AppSkeletonLoader as SkeletonLoader,
} from "@calcom/ui";
import { FiLink, FiPlus } from "@calcom/ui/components/icon";
import { ssrInit } from "@server/lib/ssr";
@@ -35,7 +35,7 @@ const ApiKeysView = () => {
return (
<Button
color="secondary"
StartIcon={Icon.FiPlus}
StartIcon={FiPlus}
onClick={() => {
setApiKeyToEdit(undefined);
setApiKeyModal(true);
@@ -75,7 +75,7 @@ const ApiKeysView = () => {
</>
) : (
<EmptyScreen
Icon={Icon.FiLink}
Icon={FiLink}
headline={t("create_first_api_key")}
description={t("create_first_api_key_description", { appName: APP_NAME })}
buttonRaw={<NewApiKeyButton />}
@@ -1,9 +1,10 @@
import { GetServerSidePropsContext } from "next";
import { useSession } from "next-auth/react";
import { Controller, useForm } from "react-hook-form";
import ThemeLabel from "@calcom/features/settings/ThemeLabel";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { APP_NAME } from "@calcom/lib/constants";
import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
@@ -46,10 +47,10 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
const AppearanceView = () => {
const { t } = useLocale();
const session = useSession();
const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
const { data: dataHasTeamPlan, isLoading: isLoadingHasTeamPlan } = trpc.viewer.teams.hasTeamPlan.useQuery();
const { isLoading: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
const formMethods = useForm({
defaultValues: {
@@ -74,7 +75,7 @@ const AppearanceView = () => {
},
});
if (isLoading || isLoadingHasTeamPlan)
if (isLoading || isTeamPlanStatusLoading)
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (!user) return null;
@@ -164,7 +165,7 @@ const AppearanceView = () => {
{/* TODO future PR to preview brandColors */}
{/* <Button
color="secondary"
EndIcon={Icon.FiExternalLink}
EndIcon={FiExternalLink}
className="mt-6"
onClick={() => window.open(`${WEBAPP_URL}/${user.username}/${user.eventTypes[0].title}`, "_blank")}>
Preview
@@ -182,18 +183,18 @@ const AppearanceView = () => {
<p className="font-semibold ltr:mr-2 rtl:ml-2">
{t("disable_cal_branding", { appName: APP_NAME })}
</p>
{!dataHasTeamPlan?.hasTeamPlan && <UpgradeTeamsBadge />}
<UpgradeTeamsBadge />
</div>
<p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p>
</div>
<div className="flex-none">
<Switch
id="hideBranding"
disabled={!dataHasTeamPlan?.hasTeamPlan}
disabled={!hasPaidPlan}
onCheckedChange={(checked) =>
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
}
checked={!dataHasTeamPlan?.hasTeamPlan ? false : value}
checked={hasPaidPlan ? value : false}
/>
</div>
</div>
@@ -225,36 +226,3 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
};
export default AppearanceView;
interface ThemeLabelProps {
variant: "light" | "dark" | "system";
value?: "light" | "dark" | null;
label: string;
defaultChecked?: boolean;
register: any;
}
const ThemeLabel = ({ variant, label, value, defaultChecked, register }: ThemeLabelProps) => {
return (
<label
className="relative mb-4 flex-1 cursor-pointer text-center last:mb-0 last:mr-0 sm:mr-4 sm:mb-0"
htmlFor={`theme-${variant}`}>
<input
className="peer absolute top-8 left-8"
type="radio"
value={value}
id={`theme-${variant}`}
defaultChecked={defaultChecked}
{...register("theme")}
/>
<div className="relative z-10 rounded-lg ring-black transition-all peer-checked:ring-2">
<img
aria-hidden="true"
className="cover w-full rounded-lg"
src={`/theme-${variant}.svg`}
alt={`theme ${variant}`}
/>
</div>
<p className="mt-2 text-sm font-medium text-gray-600 peer-checked:text-gray-900">{label}</p>
</label>
);
};
@@ -14,7 +14,6 @@ import {
Badge,
Button,
EmptyScreen,
Icon,
List,
ListItem,
ListItemText,
@@ -25,6 +24,7 @@ import {
SkeletonText,
showToast,
} from "@calcom/ui";
import { FiPlus, FiCalendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
@@ -52,7 +52,7 @@ const AddCalendarButton = () => {
return (
<>
<Button color="secondary" StartIcon={Icon.FiPlus} href="/apps/categories/calendar">
<Button color="secondary" StartIcon={FiPlus} href="/apps/categories/calendar">
{t("add_calendar")}
</Button>
</>
@@ -89,7 +89,7 @@ const CalendarsView = () => {
<div>
<div className="mt-4 flex space-x-4 rounded-md border-gray-200 bg-gray-50 p-2 sm:mx-0 sm:p-10 md:border md:p-6 xl:mt-0">
<div className=" flex h-9 w-9 items-center justify-center rounded-md border-2 border-gray-200 bg-white p-[6px]">
<Icon.FiCalendar className="h-6 w-6" />
<FiCalendar className="h-6 w-6" />
</div>
<div className="flex w-full flex-col space-y-3">
@@ -197,7 +197,7 @@ const CalendarsView = () => {
</div>
) : (
<EmptyScreen
Icon={Icon.FiCalendar}
Icon={FiCalendar}
headline={t("no_calendar_installed")}
description={t("no_calendar_installed_description")}
buttonText={t("add_a_calendar")}
@@ -15,13 +15,13 @@ import {
DropdownMenuItem,
DropdownItem,
DropdownMenuTrigger,
Icon,
List,
Meta,
showToast,
SkeletonContainer,
SkeletonText,
} from "@calcom/ui";
import { FiAlertCircle, FiMoreHorizontal, FiTrash } from "@calcom/ui/components/icon";
import AppListCard from "@components/AppListCard";
@@ -85,7 +85,7 @@ const ConferencingLayout = () => {
<div>
<Dropdown>
<DropdownMenuTrigger asChild>
<Button StartIcon={Icon.FiMoreHorizontal} variant="icon" color="secondary" />
<Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
@@ -93,7 +93,7 @@ const ConferencingLayout = () => {
type="button"
color="destructive"
disabled={app.isGlobal}
StartIcon={Icon.FiTrash}
StartIcon={FiTrash}
onClick={() => {
setDeleteCredentialId(app.credentialIds[0]);
setDeleteAppModal(true);
@@ -114,7 +114,7 @@ const ConferencingLayout = () => {
title={t("Remove app")}
description={t("are_you_sure_you_want_to_remove_this_app")}
type="confirmation"
Icon={Icon.FiAlertCircle}>
Icon={FiAlertCircle}>
<DialogFooter>
<Button color="primary" onClick={() => deleteAppMutation.mutate({ id: deleteCredentialId })}>
{t("yes_remove_app")}
@@ -5,6 +5,7 @@ import { Controller, useForm } from "react-hook-form";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { nameOfDay } from "@calcom/lib/weekday";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import {
Button,
@@ -20,7 +21,6 @@ import {
} from "@calcom/ui";
import { withQuery } from "@lib/QueryCell";
import { nameOfDay } from "@lib/core/i18n/weekday";
import { ssrInit } from "@server/lib/ssr";
+19 -8
View File
@@ -1,5 +1,6 @@
import { IdentityProvider } from "@prisma/client";
import crypto from "crypto";
import MarkdownIt from "markdown-it";
import { GetServerSidePropsContext } from "next";
import { signOut } from "next-auth/react";
import { BaseSyntheticEvent, useRef, useState } from "react";
@@ -9,6 +10,7 @@ import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { ErrorCode } from "@calcom/lib/auth";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import turndown from "@calcom/lib/turndownService";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
@@ -22,7 +24,6 @@ import {
DialogFooter,
DialogTrigger,
Form,
Icon,
ImageUploader,
Label,
Meta,
@@ -33,21 +34,25 @@ import {
SkeletonContainer,
SkeletonText,
TextField,
Editor,
} from "@calcom/ui";
import { FiAlertTriangle, FiTrash2 } from "@calcom/ui/components/icon";
import TwoFactor from "@components/auth/TwoFactor";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
import { ssrInit } from "@server/lib/ssr";
const md = new MarkdownIt("default", { html: true, breaks: true });
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonAvatar className=" h-12 w-12 px-4" />
<SkeletonButton className=" h-6 w-32 rounded-md p-5" />
<SkeletonAvatar className="h-12 w-12 px-4" />
<SkeletonButton className="h-6 w-32 rounded-md p-5" />
</div>
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@@ -229,7 +234,7 @@ const ProfileView = () => {
{/* Delete account Dialog */}
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
<DialogTrigger asChild>
<Button data-testid="delete-account" color="destructive" className="mt-1" StartIcon={Icon.FiTrash2}>
<Button data-testid="delete-account" color="destructive" className="mt-1" StartIcon={FiTrash2}>
{t("delete_account")}
</Button>
</DialogTrigger>
@@ -237,7 +242,7 @@ const ProfileView = () => {
title={t("delete_account_modal_title")}
description={t("confirm_delete_account_modal", { appName: APP_NAME })}
type="creation"
Icon={Icon.FiAlertTriangle}>
Icon={FiAlertTriangle}>
<>
<p className="mb-7">{t("delete_account_confirmation_message", { appName: APP_NAME })}</p>
{isCALIdentityProviver && (
@@ -278,7 +283,7 @@ const ProfileView = () => {
title={t("confirm_password")}
description={t("confirm_password_change_email")}
type="creation"
Icon={Icon.FiAlertTriangle}>
Icon={FiAlertTriangle}>
<>
<PasswordField
data-testid="password"
@@ -361,9 +366,15 @@ const ProfileForm = ({
<TextField label={t("email")} hint={t("change_email_hint")} {...formMethods.register("email")} />
</div>
<div className="mt-8">
<TextField label={t("about")} hint={t("bio_hint")} {...formMethods.register("bio")} />
<Label>{t("about")}</Label>
<Editor
getText={() => md.render(formMethods.getValues("bio") || "")}
setText={(value: string) => {
formMethods.setValue("bio", turndown(value), { shouldDirty: true });
}}
excludedToolbarItems={["blockType"]}
/>
</div>
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">
{t("update")}
</Button>
+96 -16
View File
@@ -1,51 +1,96 @@
import { IdentityProvider } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { identityProviderNameMap } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { userMetadata } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import { Button, Form, Meta, PasswordField, showToast } from "@calcom/ui";
import { Button, Form, Meta, PasswordField, Select, SettingsToggle, showToast } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
type ChangePasswordFormValues = {
type ChangePasswordSessionFormValues = {
oldPassword: string;
newPassword: string;
sessionTimeout?: number;
};
const PasswordView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: user } = trpc.viewer.me.useQuery();
const metadata = userMetadata.parse(user?.metadata);
const mutation = trpc.viewer.auth.changePassword.useMutation({
const sessionMutation = trpc.viewer.updateProfile.useMutation({
onSuccess: () => {
showToast(t("session_timeout_changed"), "success");
formMethods.reset(formMethods.getValues());
},
onSettled: () => {
utils.viewer.me.invalidate();
},
onMutate: async ({ metadata }) => {
await utils.viewer.me.cancel();
const previousValue = utils.viewer.me.getData();
const previousMetadata = userMetadata.parse(previousValue?.metadata);
if (previousValue && metadata?.sessionTimeout) {
utils.viewer.me.setData(undefined, {
...previousValue,
metadata: { ...previousMetadata, sessionTimeout: metadata?.sessionTimeout },
});
}
return { previousValue };
},
onError: (error, _, context) => {
if (context?.previousValue) {
utils.viewer.me.setData(undefined, context.previousValue);
}
showToast(`${t("session_timeout_change_error")}, ${error.message}`, "error");
},
});
const passwordMutation = trpc.viewer.auth.changePassword.useMutation({
onSuccess: () => {
showToast(t("password_has_been_changed"), "success");
formMethods.resetField("oldPassword");
formMethods.resetField("newPassword");
},
onError: (error) => {
showToast(`${t("error_updating_password")}, ${error.message}`, "error");
},
});
const formMethods = useForm<ChangePasswordFormValues>({
const formMethods = useForm<ChangePasswordSessionFormValues>({
defaultValues: {
oldPassword: "",
newPassword: "",
sessionTimeout: metadata?.sessionTimeout,
},
});
const {
register,
formState: { isSubmitting },
} = formMethods;
const sessionTimeoutWatch = formMethods.watch("sessionTimeout");
const handleSubmit = (values: ChangePasswordFormValues) => {
const { oldPassword, newPassword } = values;
mutation.mutate({ oldPassword, newPassword });
const handleSubmit = (values: ChangePasswordSessionFormValues) => {
const { oldPassword, newPassword, sessionTimeout } = values;
if (oldPassword && newPassword) {
passwordMutation.mutate({ oldPassword, newPassword });
}
if (metadata?.sessionTimeout !== sessionTimeout) {
sessionMutation.mutate({ metadata: { ...metadata, sessionTimeout } });
}
};
const timeoutOptions = [5, 10, 15].map((mins) => ({
label: t("multiple_duration_mins", { count: mins }),
value: mins,
}));
const isDisabled = formMethods.formState.isSubmitting || !formMethods.formState.isDirty;
return (
<>
<Meta title={t("password")} description={t("password_description")} />
@@ -68,24 +113,59 @@ const PasswordView = () => {
<Form form={formMethods} handleSubmit={handleSubmit}>
<div className="max-w-[38rem] sm:flex sm:space-x-4">
<div className="flex-grow">
<PasswordField {...register("oldPassword")} label={t("old_password")} />
<PasswordField {...formMethods.register("oldPassword")} label={t("old_password")} />
</div>
<div className="flex-grow">
<PasswordField {...register("newPassword")} label={t("new_password")} />
<PasswordField {...formMethods.register("newPassword")} label={t("new_password")} />
</div>
</div>
<p className="text-sm text-gray-600">
<p className="mt-4 max-w-[38rem] text-sm text-gray-600">
<Trans i18nKey="invalid_password_hint">
Password must be at least at least 7 characters, mix of uppercase & lowercase letters, and
contain at least 1 number
contain at least 1 number.
</Trans>
</p>
<div className="mt-8 border-t border-gray-200 py-8">
<SettingsToggle
title={t("session_timeout")}
description={t("session_timeout_description")}
checked={sessionTimeoutWatch !== undefined}
data-testid="session-check"
onCheckedChange={(e) => {
if (!e) {
formMethods.setValue("sessionTimeout", undefined, { shouldDirty: true });
} else {
formMethods.setValue("sessionTimeout", 10, { shouldDirty: true });
}
}}
/>
{sessionTimeoutWatch && (
<div className="mt-4 text-sm">
<div className="flex items-center">
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("session_timeout_after")}</p>
<Select
options={timeoutOptions}
defaultValue={
metadata?.sessionTimeout
? timeoutOptions.find((tmo) => tmo.value === metadata.sessionTimeout)
: timeoutOptions[1]
}
isSearchable={false}
className="block h-[36px] !w-auto min-w-0 flex-none rounded-md text-sm"
onChange={(event) => {
formMethods.setValue("sessionTimeout", event?.value, { shouldDirty: true });
}}
/>
</div>
</div>
)}
</div>
{/* TODO: Why is this Form not submitting? Hacky fix but works */}
<Button
color="primary"
className="mt-8"
type="submit"
disabled={isSubmitting || mutation.isLoading}>
disabled={isDisabled || passwordMutation.isLoading || sessionMutation.isLoading}>
{t("update")}
</Button>
</Form>
+7 -6
View File
@@ -5,15 +5,16 @@ import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Alert, Button, EmailField, PasswordField, TextField, HeadSeo } from "@calcom/ui";
import { asStringOrNull } from "@calcom/web/lib/asStringOrNull";
import { WEBAPP_URL } from "@calcom/web/lib/config/constants";
import prisma from "@calcom/web/lib/prisma";
import { IS_GOOGLE_LOGIN_ENABLED } from "@calcom/web/server/lib/constants";
import { ssrInit } from "@calcom/web/server/lib/ssr";
import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@calcom/ui";
import { asStringOrNull } from "../lib/asStringOrNull";
import { IS_GOOGLE_LOGIN_ENABLED } from "../server/lib/constants";
import { ssrInit } from "../server/lib/ssr";
type FormValues = {
username: string;
+23 -6
View File
@@ -1,4 +1,5 @@
import classNames from "classnames";
import MarkdownIt from "markdown-it";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -12,16 +13,21 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Avatar, Button, Icon, HeadSeo, AvatarGroup } from "@calcom/ui";
import { Avatar, Button, HeadSeo, AvatarGroup } from "@calcom/ui";
import { FiArrowRight } from "@calcom/ui/components/icon";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import Team from "@components/team/screens/Team";
import { ssrInit } from "@server/lib/ssr";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
function TeamPage({ team }: TeamPageProps) {
useTheme();
useTheme(team.theme);
const showMembers = useToggleQuery("members");
const { t } = useLocale();
const isEmbed = useIsEmbed();
@@ -36,12 +42,12 @@ function TeamPage({ team }: TeamPageProps) {
}, [telemetry, router.asPath]);
const EventTypes = () => (
<ul className="rounded-md border border-gray-200 dark:border-gray-700">
<ul className="dark:border-darkgray-300 rounded-md border border-gray-200">
{team.eventTypes.map((type, index) => (
<li
key={index}
className={classNames(
"dark:bg-darkgray-100 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600",
"dark:bg-darkgray-100 dark:border-darkgray-300 group relative border-b border-gray-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50",
!isEmbed && "bg-white"
)}>
<Link
@@ -74,6 +80,8 @@ function TeamPage({ team }: TeamPageProps) {
const teamName = team.name || "Nameless Team";
const isBioEmpty = !team.bio || !team.bio.replace("<p><br></p>", "").length;
return (
<div>
<HeadSeo
@@ -90,7 +98,14 @@ function TeamPage({ team }: TeamPageProps) {
<p className="font-cal dark:text-darkgray-900 mb-2 text-2xl tracking-wider text-gray-900">
{teamName}
</p>
<p className="dark:text-darkgray-500 mt-2 text-sm font-normal text-gray-500">{team.bio}</p>
{!isBioEmpty && (
<>
<div
className="dark:text-darkgray-600 text-sm text-gray-500 [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: md.render(team.bio || "") }}
/>
</>
)}
</div>
{(showMembers.isOn || !team.eventTypes.length) && <Team team={team} />}
{!showMembers.isOn && team.eventTypes.length > 0 && (
@@ -113,7 +128,7 @@ function TeamPage({ team }: TeamPageProps) {
<aside className="mt-8 flex justify-center text-center dark:text-white">
<Button
color="minimal"
EndIcon={Icon.FiArrowRight}
EndIcon={FiArrowRight}
className="dark:hover:bg-darkgray-200"
href={`/team/${team.slug}?members=1`}
shallow={true}>
@@ -130,6 +145,7 @@ function TeamPage({ team }: TeamPageProps) {
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug;
const team = await getTeamWithMembers(undefined, slug);
@@ -147,6 +163,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
team,
trpcState: ssr.dehydrate(),
},
};
};
+8 -4
View File
@@ -2,11 +2,11 @@ import { GetServerSidePropsContext } from "next";
import { privacyFilteredLocations, LocationObject } from "@calcom/core/location";
import { parseRecurringEvent } from "@calcom/lib";
import { getWorkingHours } from "@calcom/lib/availability";
import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { asStringOrNull } from "@lib/asStringOrNull";
import { getWorkingHours } from "@lib/availability";
import getBooking, { GetBookingType } from "@lib/getBooking";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
@@ -43,6 +43,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
slug: true,
logo: true,
hideBranding: true,
brandColor: true,
darkBrandColor: true,
theme: true,
eventTypes: {
where: {
slug: typeParam,
@@ -68,6 +71,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
},
},
title: true,
availability: true,
description: true,
@@ -150,10 +154,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: team.name || team.slug,
slug: team.slug,
image: team.logo,
theme: null as string | null,
theme: team.theme,
weekStart: "Sunday",
brandColor: "" /* TODO: Add a way to set a brand color for Teams */,
darkBrandColor: "" /* TODO: Add a way to set a brand color for Teams */,
brandColor: team.brandColor,
darkBrandColor: team.darkBrandColor,
},
date: dateParam,
eventType: eventTypeObject,
+3 -4
View File
@@ -1,5 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { LocationObject, privacyFilteredLocations } from "@calcom/app-store/locations";
import { parseRecurringEvent } from "@calcom/lib";
@@ -68,6 +67,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
slug: true,
name: true,
logo: true,
theme: true,
brandColor: true,
darkBrandColor: true,
},
},
users: {
@@ -129,9 +131,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
// FIXME: This slug is used as username on success page which is wrong. This is correctly set as username for user booking.
slug: "team/" + eventTypeObject.slug,
image: eventTypeObject.team?.logo || null,
theme: null as string | null /* Teams don't have a theme, and `BookingPage` uses it */,
brandColor: null /* Teams don't have a brandColor, and `BookingPage` uses it */,
darkBrandColor: null /* Teams don't have a darkBrandColor, and `BookingPage` uses it */,
eventName: null,
},
eventType: eventTypeObject,
+3 -2
View File
@@ -4,7 +4,8 @@ import { TeamsListing } from "@calcom/features/ee/teams/components";
import Shell from "@calcom/features/shell/Shell";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon } from "@calcom/ui";
import { Button } from "@calcom/ui";
import { FiPlus } from "@calcom/ui/components/icon";
import { ssrInit } from "@server/lib/ssr";
@@ -17,7 +18,7 @@ function Teams() {
CTA={
<Button
variant="fab"
StartIcon={Icon.FiPlus}
StartIcon={FiPlus}
type="button"
href={`${WEBAPP_URL}/settings/teams/new?returnTo=${WEBAPP_URL}/teams`}>
{t("new")}

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