Compare commits

...
130 Commits
Author SHA1 Message Date
Omar López 8143816c18 v2.5.0
release.yml / release (push) Canceled after 0s
2023-01-16 15:34:47 -07:00
Nafees Nazik 8243ac3c00 Fix: modal content hidden (#6508) 2023-01-16 18:08:34 +01:00
Alex van Andel 207e120d66 Bring back PublicShell (#6505) 2023-01-16 14:59:54 +01:00
Ronit PandaandPeer Richelsen b25d04da1c 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>
2023-01-16 12:57:38 +00:00
Alex van Andel 7064acf756 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
2023-01-16 12:18:18 +00:00
Alex van Andel 716407ad90 Added fallback to hostsFixed too which is used in COLLECTIVE (#6500) 2023-01-16 10:46:06 +00:00
GitStart-Cal.comandgitstart-calcom af0b08bb2b [CAL-677] /event-type : Event Setup sidebar item doesn't reflect multiple durations (#6390)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-01-16 11:45:54 +01:00
Hariom Balhara 3e27735ccb Disable redirect for cancellation page (#6498) 2023-01-16 09:20:50 +00:00
Nafees Nazik 76c087f29c 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
2023-01-16 08:48:07 +00:00
Peer RichelsenandAlex van Andel e05bcf98c2 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>
2023-01-15 18:54:52 +01:00
Udit TakkarandPeer Richelsen 636e88d467 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>
2023-01-15 11:40:40 +00:00
Nafees Nazik 34a61214d3 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
2023-01-14 20:11:28 +01:00
Nafees Nazik b1af30e026 fix: german translation for reschedule booking email subject (#6483)
* fix: german translation for reschedule subject

* fix: add a full stop
2023-01-14 19:10:30 +01:00
Nafees Nazik 870b59d617 fix: darkmode in booking/[id] page (#6458)
* fix: text-area style

* fix: border styles
2023-01-14 16:49:15 +01:00
Peer Richelsen e25094ccab i18n udpate 2023-01-14 02:12:07 +01:00
Carina WollendorferandCarinaWolli 1376f0991f Reusable Upgrade component for teams only features (#6473)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-14 01:48:19 +01:00
443329b99f 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>
2023-01-13 17:42:13 -07:00
Joe Au-Yeung a1fc4ec26d Disable email input if new user invited to team (#6471) 2023-01-13 17:21:57 -07:00
Udit TakkarandPeer Richelsen d462298e79 fix: remove website url in download recording (#6466)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-14 00:03:13 +00:00
Nafees NazikandPeer Richelsen 965cff38c2 fix: HOSTED_CAL_FEATURES env doesn't get passed to the frontend (#6392)
* fix: rename HOSTED_CAL_FEATURES into NEXT_PUBLIC_HOSTED_CAL_FEATURES

* chore: add the new env to example

* fix: docs link

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-13 23:51:37 +00:00
Joe Au-Yeungandkodiakhq[bot] f96e909dce Team invites - transform email or username to lower case (#6470)
* Fix string

* Use current hashPassword

* Transform email to lowercase

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-13 18:58:41 +00:00
Leo Giovanetti a4d6f7da62 Fixing GCal emailing notif and sequential deleting (#6460) 2023-01-13 11:29:42 -07:00
Nafees Nazikandkodiakhq[bot] ce9413e0dd fix: missing empty state in /apps/installed/web3 (#6377)
* refactor: turn enum into const assertion

* feat: add web3 to icon list

* fix: type

* fix: type cast query params and add category list

* fix: conditional rendering

* feat: add i18n keys

* fix: type errors

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-13 14:12:43 +00:00
Peer Richelsen 8fc0dfb763 booking page: only scroll to bottom on mobile (#6453)
* move useMediaQuery into packages

* revert logo.tsx
2023-01-13 17:40:02 +05:30
690edcf0a7 New Crowdin translations by Github Action (#6444)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-12 23:21:06 +00:00
Omar López b7dbf10068 v2.4.7
release.yml / release (push) Canceled after 0s
2023-01-12 16:10:07 -07:00
zomars 83b4ba94fc testing auto crowdin 2023-01-12 16:01:40 -07:00
62a09e7098 Small improvements for constants (#6409)
* use IS_SELF_HOSTED for CalComVersion instead of an additional check for hosted and self-hosted versions

* use IS_SELF_HOSTED for HOSTED_CAL_FEATURES constant to prevent an additional check

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-12 22:25:25 +00:00
d2c4e2894d Check for session in addition for isPublic prop (#6443)
* Check for session in addition for isPublic prop

* Cleaner, more readable version

* Less code same result, no rerender

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2023-01-12 22:11:15 +00:00
Joe Au-YeungandOmar López 070d314897 Increase error logging details (#6442)
Co-authored-by: Omar López <zomars@me.com>
2023-01-12 21:43:40 +00:00
zomars 0a76368e98 Run required checks from Crowin PRs 2023-01-12 14:22:11 -07:00
99287d9eb4 Feature/fixed hosts (#6423)
* Save design updates for fixed round robin support

* wip - added Host relation

* DRY hostsFixed select

* Changes to allow isFixed in the Availability page

* Allow booking with fixed hosts

* Replace users with hosts if hosts is set

* Also prefer hosts over users here

* Prevent duplicates when hosts is saved

* Accidental slot duplication

* Attempt at making isFixed optional

* Sydney and Shiraz can live in harmony again

* No fixed hosts causes every to be true..

* Make hosts undefinable

* Small handleNewBooking fixes

* Similar fix to the hosts to check for empty-ness

* Default to isFixed false instead of true

* Fix event type list avatars

* Filter availableTimeSlots, collective ts's wont magically re-enable.

* (Further) Fixes to getAggregateWorkingHours

* Weird userId artifact that preceeds this branch, investigate later

* On user delete, remove host, on event type delete, also remove host

* Dynamic event types were incorrectly marked as isFixed=false

* Fixed notFound error when there are no users (but hosts)

* Fixes userIsOwner

* Oops, fixed isFixed users being included correctly

* Fixed Button styling on secondary darkmode

* Create exclusivity in selectable options

* fix: Location dropdown is overflowing #6376 (#6415)

* add `menuPlacement` react-select's props to `getReactSelectProps` to avoid dropdown overflow on small screen.
By default, set to "auto".

* CALCOM-6362 - [CAL-728] App Sidebar. Child items aren't sized/spaced properly (#6424)

* [CAL-728] App Sidebar. Child items aren't sized/spaced properly

* fix: undo logo size

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: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* Fixing colors (#6429)

* Update website

* Update console

* Update yarn.lock

* Uses disable instead of filtering to be more clear

* Update EventTeamTab.tsx

* Merge conflict cleanup

* During test cases the dayjs() utcOffset is local time, this fixes that

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: Thomas Brodusch <3238312+thomasbrodusch@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2023-01-12 14:09:12 -07:00
zomars 100035cb89 Update yarn.lock 2023-01-12 11:37:19 -07:00
zomars 1750dbead6 Update console 2023-01-12 11:37:11 -07:00
zomars 4c8ed3d11f Update website 2023-01-12 11:36:54 -07:00
sean-brydon f21c4eb11e Fixing colors (#6429) 2023-01-12 16:57:43 +00:00
3f71a41d7e CALCOM-6362 - [CAL-728] App Sidebar. Child items aren't sized/spaced properly (#6424)
* [CAL-728] App Sidebar. Child items aren't sized/spaced properly

* fix: undo logo size

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: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-12 16:44:41 +00:00
Thomas Brodusch 1ba191c4ed fix: Location dropdown is overflowing #6376 (#6415)
* add `menuPlacement` react-select's props to `getReactSelectProps` to avoid dropdown overflow on small screen.
By default, set to "auto".
2023-01-12 09:29:37 -07:00
Nafees NazikandPeer Richelsen 6414903567 fix: guess correct timezone when onboarding (#6430)
* fix: guess correct timezone

* fix: redirect if on boarded

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-12 16:17:57 +00:00
e62d8b4596 fix: long words break UI (#6410)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-12 09:05:20 -07:00
Carina WollendorferandCarinaWolli e27b1b1bc2 Removes 'Test Action' button in workflows (#6418)
* comment out test action feature

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-12 09:03:30 -07:00
zomars 9507138f29 Labeler fixes 2023-01-12 08:52:21 -07:00
GitStart-Cal.comandgitstart-calcom e5fa202a09 CALCOM-6398 - [CAL-749] remove ethSignature from query if unused (#6425)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-01-12 14:55:00 +01:00
Udit TakkarandAlex van Andel 447098bc97 fix: use i18n properly in utils (#6363)
* fix: use i18n properly in utils

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

* chore

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-12 05:22:55 +00:00
Udit Takkar dc5e042cdd fix: remove flex-nowrap (#6416)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-12 01:24:39 +00:00
Leo Giovanetti 92fb365b54 Fix for Request Email Reminder (#6421)
* Fix

* Selecting id
2023-01-11 22:12:11 -03:00
161f5c7000 Adds "Upgrade" paywall to Workflow SMS features instead of disabling them (#6412)
* adds upgrade badge to dropdown

* add space between batch and check

* add upgrade tool tip

* fix import

* code clean up

* fix type error

* fix type error

* fix type errors

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-11 18:41:45 +00:00
c8744768c4 Refactored buildSlots (#6389)
* Refactored buildSlots

* Added typing for computedAvailableSlots

* Adds very simple boundary logic to make sure slots start at the right times

* Logic was cutting off too early

* Woops, should be eventLength, not frequency

* Expect 12:15 slots as availability is till 12:30 for booking

* Deliberately skip test for testing purposes

* slotInterval overrides intervals, but not duration

* Fix calendar mocking

* Another 45m length

* Unskip :(

* Fix test

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2023-01-11 17:33:34 +00:00
Udit TakkarandPeer Richelsen b34a568a93 fix: show recordings even to free plan users (#6411)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-11 09:58:50 -07:00
sean-brydon a669403604 Fix avatars not overlapping (#6405)
* Fix avatars being weird

* Fix useravatar import

* Fix type error

* Fixing spacing
2023-01-11 15:00:46 +00:00
Peer Richelsen 7b2bfc2a64 added GITHUB_API_REPO_TOKEN for console (#6408) 2023-01-11 14:47:37 +00:00
Alex van Andel 7655c0c867 Testing in isolation 2023-01-11 13:24:02 +00:00
Nafees Nazikandsean-brydon 6fbabb4b26 fix: remove border (#6391)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-01-11 10:28:57 +00:00
sean-brydon edfc9ff082 Fixes ultrawide breaking booking limits selects (#6396) 2023-01-11 10:16:04 +00:00
Leo Giovanetti 59a9a77e3f Fixes cancelling all recurring events (#6384) 2023-01-10 14:09:28 -07:00
Joe Au-Yeungandkodiakhq[bot] 8e756d7648 Fix custom event names in emails (#6375)
* Fix What for normal bookings

* Fix subject on bookings with seats

* WIP

* Change more strings

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-10 18:32:50 +00:00
Udit Takkarandsean-brydon 5763c1c2f1 fix: use default length in form (#6378)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-01-10 17:25:09 +00:00
Alex van Andel 6c5ae0b4d9 Fixed default value for ordering correctly (#6372) 2023-01-10 17:06:53 +01:00
9cb9ce2e42 Chor/bulk UI migration (#6367)
* Big bulk commit

* Switch and settings toggle

* Popover Kbar Timezones

* Image Uploader

* Fix core export

* Meta

* Swatch

* Remove shell

* Fix shell imports

* Moved ShellSubHeading component to UI, to prevent recursive imports from shell component

* Removed shell from ui ts config since shell doesnt have a dependency on shell anymore.

Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Omar López <zomars@me.com>
2023-01-10 15:39:29 +00:00
Omar LópezandCrowdin Bot 018be43017 New Crowdin translations by Github Action (#6365)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-10 08:11:59 -07:00
zomars 4f720c8c9b Update crowdin.yml 2023-01-10 08:05:14 -07:00
Udit TakkarandPeer Richelsen 06e1e113f0 fix: margin and padding (#6358)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-10 12:44:30 +00:00
sean-brydonandOmar López 3fc6f38da9 Chor/more UI migrations (#6353)
* Move tabs to components

* Lots of form components

* Update apps/web/components/apps/layouts/InstalledAppsLayout.tsx

Co-authored-by: Omar López <zomars@me.com>
2023-01-10 12:25:39 +00:00
Omar López 8e8c96c267 v2.4.6
release.yml / release (push) Canceled after 0s
2023-01-09 20:26:21 -07:00
github-actions[bot]andCrowdin Bot fab765c5ee New Crowdin translations by Github Action (#6349)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-09 20:25:22 -07:00
zomars 89ed07a097 Update crowdin.yml 2023-01-09 20:17:03 -07:00
zomars d7d73d2caf Update crowdin.yml 2023-01-09 20:15:53 -07:00
zomars aa92730e28 Fix labeler 2023-01-09 19:52:02 -07:00
430430e0cd Feedback of reusable wysiwyg editor (#6357)
* make text editor reusable

* use dropdown component for block type

* remove ring when clicking on block type

* allow excluding items in toolbar

* refactor code

* make AddVariableDropdown reusable

* fixed missed translations

* fixes variable translation issue

* change missed translation variables

* make AddVariablesDropdown reusable

* reorder block types in dropdown

* remove not needed prop

* code clean up

* center add variable dropwdown icon

* remove rounded borders in dropwdown on hover and active

* center icons

* fix tool bar items dropdown

* add constant for dynamic text variables

* remove not needed variables (added from merge)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-01-09 19:22:17 -07:00
62713940b0 Chor/v2 more tidyup - Card - link icon button - tooltip (#6312)
* Card

* Step card

* Already in components

* Link Icon Button

* Tooltip

* Fix types

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 19:22:04 -07:00
75aef09338 Google Meet - installable app (#5904)
* Abstract app category navigation

* Send key schema to frontend

Co-authored-by: Omar López <zomars@users.noreply.github.com>

* Render keys for apps on admin

* Add enabled col for apps

* Save app keys to DB

* Add checks for admin role

* Abstract setup components

* Add AdminAppsList to setup wizard

* Migrate to v10 tRPC

* Default hide keys

* Display enabled apps

* Merge branch 'main' into admin-apps-ui

* Toggle calendars

* WIP

* Add params and include AppCategoryNavigation

* Refactor getEnabledApps

* Add warning for disabling apps

* Fallback to cal video when a video app is disabled

* WIP send disabled email

* Send email to all users of  event types with payment app

* Disable Stripe when app is disabled

* Disable apps in event types

* Send email to users on disabled apps

* Send email based on what app was disabled

* WIP type fix

* Disable navigation to apps list if already setup

* UI import fixes

* Waits for session data before redirecting

* Updates admin seeded password

To comply with admin password requirements

* Update yarn.lock

* Flex fixes

* Adds admin middleware

* Clean up

* WIP

* WIP

* NTS

* Add dirName to app metadata

* Upsert app if not in db

* Upsert app if not in db

* Add dirName to app metadata

* Add keys to app packages w/ keys

* Merge with main

* Toggle show keys & on enable

* Fix empty keys

* Fix lark calendar metadata

* Fix some type errors

* Fix Lark metadata & check for category when upserting

* More type fixes

* Fix types & add keys to google cal

* WIP

* WIP

* WIP

* More type fixes

* Fix type errors

* Fix type errors

* More type fixes

* More type fixes

* More type fixes

* Feedback

* Fixes default value

* Feedback

* Migrate credential invalid col default value "false"

* Upsert app on saving keys

* Clean up

* Validate app keys on frontend

* Add nonempty to app keys schemas

* Add web3

* Listlocale filter on categories / category

* Grab app metadata via category or categories

* Show empty screen if no apps are enabled

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Replace .nonempty() w/ .min(1)

* Fix type error

* Address feedback

* Draft Google Meet install button

* Add install button and warning dialog

* WIP

* WIP

* Display warning when Meet is selected

* Display Google Meet warning on email to organizer

* Fix email

* Fix type errors

* Fix type error

* Add connected account component

* Add warning message

* Address comments

* Address feedback

* Clean up & add MeetLocationType

* Use useApp hook

* Translate to new API approach

* Remove console.log

* Refactor

* Fix missing backup Cal video link

* WIP

* Address feedback

* Update submodules

* Feedback

* Submodule sync

Co-authored-by: Omar López <zomars@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-01-10 02:01:57 +00:00
4e65c30e18 fix: Parameterize Stripe Payment Fee configs (#6024)
* Parameterize Stripe Payment Fee configs

* Restore constant values for Payment Fee params

* Restore constant values for all Payment Fee params

* Remove STRIPE_ prefix on payment fee vars

* Cast Payment Fee params as Number

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2023-01-09 18:50:49 -07:00
8b873e2828 Adding a bunch of performance markers (#6303)
* Adding a bunch of performance markers

* Update packages/lib/server/perfObserver.ts

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 18:42:46 -07:00
zomars e70d179628 Update website 2023-01-09 18:31:38 -07:00
Nafees Nazik ca9a836300 fix: remove padding from username field (#6356) 2023-01-09 19:35:00 +01:00
Alex van Andel abdf72dc0f Fixup yarn.lock 2023-01-09 14:36:49 +00:00
dependabot[bot]andBailey Pumfleet 227cd165f6 Bump json5 from 1.0.1 to 1.0.2 (#6351)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-01-09 14:19:51 +00:00
a0135cdc59 Make WYSIWYG editor reusable (#6293)
* make text editor reusable

* use dropdown component for block type

* remove ring when clicking on block type

* allow excluding items in toolbar

* refactor code

* make AddVariableDropdown reusable

* fixed missed translations

* fixes variable translation issue

* change missed translation variables

* make AddVariablesDropdown reusable

* reorder block types in dropdown

* remove not needed prop

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 14:15:11 +00:00
112d504148 Chor/v2 UI tidy - Dialog & Toasts (#6307)
* Dialog Tidyup

* Move toasts

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-01-09 13:57:12 +00:00
Peer Richelsen d275930732 link to event-types in settings to fix cal.com usage (#6348) 2023-01-09 13:15:10 +01:00
Alex van Andel 97e33bdd7e Set bg-white to secondary variant (#6339) 2023-01-08 17:50:37 +01:00
Hariom Balhara 3f13791faf Add app-store installed page navigationt ests (#6333) 2023-01-08 09:29:20 +01:00
Omar LópezandAlex van Andel 359cfc4be0 Replaces mdx-remote with markdown-it (#6326)
* Replaces mdx-remote with react-markdown

* Replaces react-markdown with already available markdown-it

* Renames readme to description

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-08 00:05:35 +00:00
Alex van AndelandPeer Richelsen 1ad6bba0ab set useQueryParam to false for app categories (#6330)
* set useQueryParam to false for app categories

* New param 'useQueryParam' for AppCategoryNavigation/AdminAppsList

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 23:21:21 +00:00
Alex van Andel 6dc4211d81 Fixes hydration error, uses useTypedQuery for tabName (#6327) 2023-01-07 22:42:30 +00:00
Alex van Andel e5f0452728 Don't initialize KBar when unauthed (#6328)
* Don't initialize KBar when unauthed

* Don't render trigger when query is undefined
2023-01-07 22:31:15 +00:00
zomars d95bba63f4 Submodule sync 2023-01-07 11:19:16 -07:00
zomars 6f48869021 Add missing slash to custom import 2023-01-07 10:17:38 -07:00
Jeff Loiselle 845bfae887 adds event types to kbar (#6210) 2023-01-07 10:06:53 -07:00
zomars 3fe02fba26 Update basic.e2e.ts 2023-01-07 09:55:30 -07:00
zomars 4ca5ca9fe2 Update hash-my-url.e2e.ts 2023-01-07 09:50:02 -07:00
Hariom BalharaandPeer Richelsen 364e187080 Fix/Next.js-13/Circular Dependency in Routing Forms (#6321)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 15:50:28 +00:00
Peer Richelsen b1193eef1e Update README.md 2023-01-07 16:27:56 +01:00
Peer Richelsen e110f12cf1 Update README.md 2023-01-07 16:26:47 +01:00
Peer Richelsen 022b513935 Update package.json 2023-01-07 11:55:50 +01:00
Jeff LoiselleandPeer Richelsen 010f39a7a4 adds render.com url constant (#6287)
* adds render.com url constant

* fixes webapp_url constant

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 10:28:02 +00:00
Peer Richelsen 6b6eed66eb fixed mobile sidebar in settings (#6317) 2023-01-07 10:10:35 +00:00
Udit Takkar c214e01f03 fix: use overflow-auto (#6295)
* fix: use overflow-auto

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

* fix: scroll issues with too many filters

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

* chore: merge conflict

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-07 11:00:11 +01:00
Carina WollendorferandCarinaWolli a28ac3634d Add start and end time of rescheduled booking to webhook payload (#6297)
* add start and end time of rescheduled booking

* fix type error

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-06 13:37:06 -07:00
zomars a0709fac88 Circular dependecy hotfix 2023-01-06 13:15:34 -07:00
Alex van Andel e5b6a3332e Feature/new design radio area group (#6289)
* Refactor of the RadioAreaGroup component

* Move RadioAreaGroup outside of v2/core

* Use useTypedQuery

* Removed unused (broken) imports

* Fix type for Select
2023-01-06 20:05:23 +00:00
Jeff Loiselle 979686920a fixes codespace login msg spelling (#6288) 2023-01-06 12:37:03 -07:00
zomars 793850f3aa Update console 2023-01-06 11:10:17 -07:00
Nafees NazikandPeer Richelsen fa9d72fc0a fix: flash of untranslated title in settings/my-account/profile (#6163)
* fix: flash of untranslated content

* feat: extract loading into a variable

* fix: use optional chaining because typescript complaining

* feat: render Meta component even if not loading for seo purpose

* fix: received an array with more than 1 element as
children warning

* fix: changes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-06 10:35:32 -07:00
zomars 0d485ffed6 Updates website 2023-01-06 10:18:18 -07:00
Peer Richelsen 9a2e55d528 revert version change 2023-01-06 16:59:26 +01:00
github-actions[bot]andCrowdin Bot 81ecbb86a5 New Crowdin translations by Github Action (#6308)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-06 16:58:09 +01:00
Peer Richelsen 2cd816932b Update package.json 2023-01-06 16:55:01 +01:00
Peer Richelsenandzomars d83411162d Upgrade to next 13.1 (#6265)
Co-authored-by: zomars <zomars@me.com>
2023-01-06 12:13:56 +00:00
Nafees NazikandPeer Richelsen c9c54db3a1 fix: remove duplicate font preload (#6291)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-06 12:35:48 +01:00
Udit Takkarandkodiakhq[bot] d100600789 feat: update team schema, update team query (#6294)
team appearance section

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: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-06 10:55:57 +00:00
Omar López 6f9011a167 Update package.json
release.yml / release (push) Canceled after 0s
2023-01-05 18:41:30 -07:00
Omar López ae004c8a6c Fixes extension check for URL with dots in segments (#6301) 2023-01-05 16:38:44 -07:00
Omar López ccbb16fe76 Upgrades prisma (#6298) 2023-01-05 16:24:17 -07:00
Omar López b77ce4a010 Allows to override default debug level for logger (#6300) 2023-01-05 16:23:50 -07:00
Omar Lópezandalannnc f72f709e34 Adds availablity and schedule indexes (#6296)
Co-authored-by: alannnc <alannnc@gmail.com>
2023-01-05 22:29:03 +00:00
Hariom Balhara 37ceb25f8c Make sure returnTo is correct URL (#6189)
* make sure returno is safe

* More fixes
2023-01-05 12:55:55 -07:00
sean-brydon 0141d8f954 Adding border to tabs (#6284)
* Adding border to tabs

* Fix rounded

* Fixing mb on >= lg
2023-01-05 17:14:27 +00:00
sean-brydon 47fe791006 Tidy up v2 layouts (#6266)
* Lots of places use layouts huh

* Fix imports

* FIx merge removing rtl settings

* Fixing Icon Import

* Fix shell import

* Fix tab exports
2023-01-05 18:00:16 +01:00
Jeroen Reumkensandsean-brydon 0cbaba73e4 🧹 Chore/move v2 components to components dir (#6261)
* Removed old root UI components.

* Fixed import path

* Moved first v2 components to new components dir.

* Changed icon to not import from root, since it somehow creates a recursive import

* Removed irrelevant todo

* Added docs for new components

* Fixed type errors

* Update packages/ui/components/index.ts

Removing as per comment

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-01-05 12:04:28 +00:00
Hariom Balhara aaac491dc7 HotFix: Admin Apps Listing (#6282) 2023-01-05 17:20:41 +05:30
zomars fc4203e963 Update api 2023-01-04 16:05:48 -07:00
Hariom Balhara 8430e9612e Revive getSchedule Tests and add more (#6200)
* Revive getSchedule Tests

* Cleanup tests

* More Tests

* Add tests for date override
2023-01-04 15:40:06 -07:00
Jeff Loiselle de81934619 adds devcontainer/codespaces support (#6250) 2023-01-04 15:29:25 -07:00
Jeff Loiselle 5aedcbee55 adds render.com deploy button (#6276) 2023-01-04 23:22:07 +01:00
7bc8d7e991 New Crowdin translations by Github Action (#6274)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-04 23:20:54 +01:00
LoneRifleandPeer Richelsen df183f5882 chore(web): update .nvmrc to node 16 (#6214)
Follow up on #2711 by ensuring that `app/web/.nvmrc` lines up with `.nvmrc`

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-04 15:20:05 -07:00
27d9fad292 Feature/whitelabel part 2 (#5821)
* applied whitelabel to additional parts of the app

* format .md files

* updated metadata of all apps

* applied whitelabel to PoweredByCal.tsx

* fixed `team_upgrade_banner_action` with better german

* fixed import of APP_NAME in email templates

* revert "applied whitelabel" in CalEventParser.test.ts

* Revert "updated metadata of all apps"

This reverts commit 9c1621788d2c4c4a4e4a545f48898d4af7a39f33.

* revert app-store whitelabel changes

* Revert "applied whitelabel to PoweredByCal.tsx"

This reverts commit f2b2d79aa51cca3a70af6bf649b24e22f2fca73a.

* Update website

* Sync

* Update constants.ts

* Prevent unit test error

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2023-01-04 22:14:46 +00:00
zomars bed798d440 Formatting 2023-01-04 13:49:20 -07:00
zomars e5a8a4e34d Submodule sync 2023-01-04 13:47:13 -07:00
488 changed files with 7411 additions and 5543 deletions
+42
View File
@@ -0,0 +1,42 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker
{
"name": "Docker outside of Docker",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
"features": {
"ghcr.io/devcontainers/features/docker-from-docker:1": {
"version": "latest",
"enableNonRootDocker": "true",
"moby": "true"
},
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers-contrib/features/npm-package:1": {},
"ghcr.io/devcontainers-contrib/features/jest:2": {},
"ghcr.io/devcontainers-contrib/features/prisma:2": {},
"ghcr.io/guiyomh/features/vim:0": {}
},
// Use this environment variable if you need to bind mount your local source code into a new container.
"remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
},
"hostRequirements": {
"cpus": 4,
"memory": "8gb"
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "./deploy/install.sh"
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
+9 -1
View File
@@ -31,11 +31,14 @@ NEXT_PUBLIC_CONSOLE_URL='http://localhost:3004'
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
# To enable SAML login, set both these variables
# @see https://github.com/calcom/cal.com/tree/main/packages/ee#setting-up-saml-login
# @see https://github.com/calcom/cal.com/tree/main/packages/features/ee#setting-up-saml-login
# SAML_DATABASE_URL="postgresql://postgres:@localhost:5450/cal-saml"
SAML_DATABASE_URL=
# SAML_ADMINS='pro@example.com'
SAML_ADMINS=
# NEXT_PUBLIC_HOSTED_CAL_FEATURES=1
NEXT_PUBLIC_HOSTED_CAL_FEATURES=
# If you use Heroku to deploy Postgres (or use self-signed certs for Postgres) then uncomment the follow line.
# @see https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js
# PGSSLMODE='no-verify'
@@ -102,6 +105,8 @@ STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
PAYMENT_FEE_FIXED=
PAYMENT_FEE_PERCENTAGE=
# Use for internal Public API Keys and optional
API_KEY_PREFIX=cal_
@@ -147,3 +152,6 @@ SENTRY_IGNORE_API_RESOLUTION_ERROR=
NEXT_PUBLIC_APP_NAME="Cal.com"
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
NEXT_PUBLIC_COMPANY_NAME="Cal.com, Inc."
# Set this to true in to disable new signups
# NEXT_PUBLIC_DISABLE_SIGNUP=true
NEXT_PUBLIC_DISABLE_SIGNUP=
-6
View File
@@ -1,9 +1,3 @@
"♻️ autoupdate":
- apps/web/public/static/locales/**/*.json
automerge:
- apps/web/public/static/locales/**/*.json
"❗️ migrations":
- packages/prisma/migrations/**/migration.sql
+3
View File
@@ -9,6 +9,8 @@ concurrency:
cancel-in-progress: true
jobs:
required-checks:
uses: calcom/cal.com/.github/workflows/required-checks.yml@main
synchronize-with-crowdin:
runs-on: ubuntu-latest
@@ -21,6 +23,7 @@ jobs:
with:
upload_translations: true
download_translations: true
pull_request_labels: "♻️ autoupdate, automerge"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
+1
View File
@@ -7,6 +7,7 @@ on:
paths:
- apps/web/**
- packages/embeds/**
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
+1 -2
View File
@@ -1,13 +1,12 @@
name: E2E test
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request_target: # So we can test on forks
branches:
- main
paths:
- apps/web/**
- packages/**
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
+2
View File
@@ -14,3 +14,5 @@ jobs:
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
sync-labels: ""
+1 -2
View File
@@ -1,10 +1,9 @@
name: "Meta Workflow: Require Conditional Status Checks"
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
pull_request:
branches:
- main
workflow_call:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
+3 -2
View File
@@ -1,13 +1,14 @@
name: Unit tests
on:
push:
branches: [ feat/routing-forms/reporting ] # TODO: Remove this after merged in main
pull_request_target: # So we can test on forks
branches:
- main
paths:
- apps/web/**
- packages/**
workflow_run:
workflows: [Crowdin Action]
types: [completed]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
+15 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -17,7 +17,7 @@
"license": "MIT",
"dependencies": {
"iframe-resizer-react": "^1.1.0",
"next": "^12.3.1",
"next": "^13.1.1",
"nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.2",
"react": "^18.2.0",
+3 -3
View File
@@ -31,7 +31,7 @@ All apps can be found under `packages/app-store`. In this folder is `_example` w
| ├──index.ts
| ├──package.json
| ├──.env.example
| ├──README.mdx
| ├──DESCRIPTION.md
```
## Getting Started
@@ -48,9 +48,9 @@ Under the `/components` folder, this is where the install button for your app sh
The `/lib` folder is where the functions of your app live. For example, when creating a booking with a MS Teams link the function to make the call to grab the link lives in the `/lib` folder. Export your endpoints in an `index.ts` file under `/lib` folder and import them in your main `index.ts` file.
On the app store page you can customize your apps description by adding a markdown file called `README.mdx`. If you do not add one then the description from you `package.json` will be used instead.
On the app store page you can customize your apps description by adding a markdown file called `DESCRIPTION.md`. If you do not add one then the description from you `package.json` will be used instead.
The `/static` folder is where you can store your app icon and any images that your `README.mdx` may use.
The `/static` folder is where you can store your app icon and any images that your `DESCRIPTION.md` may use.
## Adding Your App to the App Store
To render your app on the app store page, go to `packages/app-store/index.ts`. Import your app into the file and add it to the `appStore` object.
+1 -1
View File
@@ -20,7 +20,7 @@
"@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"next": "^12.3.1",
"next": "^13.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
+1 -1
View File
@@ -14,7 +14,7 @@
"dependencies": {
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "^12.3.1",
"next": "^13.1.1",
"openapi-snippet": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+1 -1
View File
@@ -1 +1 @@
14.17
v16
-9
View File
@@ -1,9 +0,0 @@
import { Suspense, SuspenseProps } from "react";
/**
* Wrapper around `<Suspense />` which will render the `fallback` when on server
* Can be simply replaced by `<Suspense />` once React 18 is ready.
*/
export const ClientSuspense = (props: SuspenseProps) => {
return <>{typeof window !== "undefined" ? <Suspense {...props} /> : props.fallback}</>;
};
+7 -7
View File
@@ -504,7 +504,7 @@ const tabs = [
return (
<>
<div>
<small className="flex py-4 text-neutral-500">
<small className="flex py-4 text-gray-500">
{t("place_where_cal_widget_appear", { appName: APP_NAME })}
</small>
</div>
@@ -556,7 +556,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
}
return (
<>
<small className="flex py-4 text-neutral-500">{t("create_update_react_component")}</small>
<small className="flex py-4 text-gray-500">{t("create_update_react_component")}</small>
<TextArea
data-testid="embed-react"
ref={ref as typeof ref & MutableRefObject<HTMLTextAreaElement>}
@@ -650,7 +650,7 @@ const ChooseEmbedTypesDialogContent = () => {
<div className="order-none box-border flex-none rounded-sm border border-solid bg-white">
{embed.illustration}
</div>
<div className="mt-2 font-medium text-neutral-900">{embed.title}</div>
<div className="mt-2 font-medium text-gray-900">{embed.title}</div>
<p className="text-sm text-gray-500">{embed.subtitle}</p>
</button>
))}
@@ -827,7 +827,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
<CollapsibleTrigger
type="button"
className="flex w-full items-center text-base font-medium text-neutral-900">
className="flex w-full items-center text-base font-medium text-gray-900">
<div>
{embedType === "inline"
? "Inline Embed Customization"
@@ -838,7 +838,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
<Icon.FiChevronRight
className={`${
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
} ml-auto h-5 w-5 text-gray-500`}
/>
</CollapsibleTrigger>
<CollapsibleContent className="text-sm">
@@ -1001,11 +1001,11 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
open={isBookingCustomizationOpen}
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
<CollapsibleTrigger className="flex w-full" type="button">
<div className="text-base font-medium text-neutral-900">Cal Booking Customization</div>
<div className="text-base font-medium text-gray-900">Cal Booking Customization</div>
<Icon.FiChevronRight
className={`${
isBookingCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
} ml-auto h-5 w-5 text-gray-500`}
/>
</CollapsibleTrigger>
<CollapsibleContent>
+3 -3
View File
@@ -62,12 +62,12 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
const className = tab.className || "";
return (
<Component key={tab.name}>
<Link key={tab.name} href={href} {...linkProps}>
<Link key={tab.name} href={href} {...linkProps} legacyBehavior>
<a
onClick={onClick}
className={classNames(
isCurrent
? "border-neutral-900 text-neutral-900"
? "border-neutral-900 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
className
@@ -76,7 +76,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
{tab.icon && (
<tab.icon
className={classNames(
isCurrent ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
isCurrent ? "text-gray-900" : "text-gray-400 group-hover:text-gray-500",
"-ml-0.5 hidden h-4 w-4 ltr:mr-2 rtl:ml-2 sm:inline-block"
)}
aria-hidden="true"
+2 -1
View File
@@ -1,6 +1,7 @@
import React, { ComponentProps } from "react";
import { ErrorBoundary, Icon, Shell } from "@calcom/ui";
import Shell from "@calcom/features/shell/Shell";
import { ErrorBoundary, Icon } from "@calcom/ui";
import NavTabs from "./NavTabs";
+6 -5
View File
@@ -6,14 +6,13 @@ import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { InstallAppButton } from "@calcom/app-store/components";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import Shell from "@calcom/features/shell/Shell";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App";
import { Button, Icon, Shell, showToast, SkeletonButton, SkeletonText } from "@calcom/ui";
import HeadSeo from "@components/seo/head-seo";
import { Button, Icon, showToast, SkeletonButton, SkeletonText, HeadSeo } from "@calcom/ui";
const Component = ({
name,
@@ -100,8 +99,10 @@ const Component = ({
<h1 className="font-cal ml-4 text-3xl text-gray-900">{name}</h1>
</div>
<h2 className="text-sm font-medium text-gray-600">
<Link href={`categories/${categories[0]}`}>
<a className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">{categories[0]}</a>
<Link
href={`categories/${categories[0]}`}
className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">
{categories[0]}
</Link>{" "}
• {t("published_by", { author })}
</h2>
@@ -13,10 +13,10 @@ import {
EmptyScreen,
Icon,
List,
ShellSubHeading,
showToast,
AppSkeletonLoader as SkeletonLoader,
Switch,
ShellSubHeading,
} from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
@@ -184,9 +184,7 @@ function ConnectedCalendarsList(props: Props) {
<div className="border-t border-gray-200">
{!fromOnboarding && (
<>
<p className="px-4 pt-4 text-sm text-neutral-500">
{t("toggle_calendars_conflict")}
</p>
<p className="px-4 pt-4 text-sm text-gray-500">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-2 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
@@ -330,7 +328,10 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
})}
description={t(`no_category_apps_description_calendar`)}
buttonRaw={
<Button color="secondary" href="/apps/categories/calendar">
<Button
color="secondary"
data-testid="connect-calendar-apps"
href="/apps/categories/calendar">
{t(`connect_calendar_apps`)}
</Button>
}
@@ -2,24 +2,10 @@ import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
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 Shell from "../Shell";
import type { HorizontalTabItemProps } from "../navigation/tabs/HorizontalTabItem";
import HorizontalTabs from "../navigation/tabs/HorizontalTabs";
const tabs: HorizontalTabItemProps[] = [
{
name: "app_store",
href: "/apps",
},
{
name: "installed_apps",
href: "/apps/installed",
},
];
type AppsLayoutProps = {
children: React.ReactNode;
actions?: (className?: string) => JSX.Element;
@@ -34,11 +20,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
if (session.status === "loading") return <></>;
return (
<Shell {...rest} actions={actions?.("hidden sm:block")}>
<Shell {...rest} actions={actions?.("block")}>
<div className="flex flex-col xl:flex-row">
<div className="block lg:hidden">
<HorizontalTabs tabs={tabs} actions={actions?.("ml-6 mr-4 mt-3 block sm:hidden")} />
</div>
<main className="w-full">
{emptyStore ? (
<EmptyScreen
@@ -2,12 +2,10 @@ import React, { ComponentProps } from "react";
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
import { InstalledAppVariants } from "@calcom/app-store/utils";
import Shell from "@calcom/features/shell/Shell";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "../../../components/icon";
import Shell from "../Shell";
import type { HorizontalTabItemProps } from "../navigation/tabs/HorizontalTabItem";
import type { VerticalTabItemProps } from "../navigation/tabs/VerticalTabItem";
import { Icon } from "@calcom/ui";
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{
@@ -46,13 +44,15 @@ export default function InstalledAppsLayout({
children,
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const variant: typeof InstalledAppVariants[number] = "payment";
const query = trpc.viewer.integrations.useQuery({
variant: InstalledAppVariants.payment,
variant,
onlyInstalled: true,
});
let actualTabs = tabs;
if (query.data?.items.length === 0) {
actualTabs = tabs.filter((tab) => tab.name !== InstalledAppVariants.payment);
actualTabs = tabs.filter((tab) => tab.name !== variant);
}
return (
@@ -2,9 +2,10 @@ import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import React, { ComponentProps, useEffect } from "react";
import { ErrorBoundary } from "../../..";
import Shell from "../Shell";
import SettingsLayout from "./SettingsLayout";
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
import Shell from "@calcom/features/shell/Shell";
import { ErrorBoundary } from "@calcom/ui";
import { UserPermissionRole } from ".prisma/client";
export default function AdminLayout({
+24 -24
View File
@@ -87,7 +87,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
slug: eventTypeSlug,
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
ethSignature,
...(ethSignature ? { ethSignature } : {}),
},
};
@@ -114,29 +114,29 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
</div>
) : (
<Link href={bookingUrl} prefetch={false}>
<a
className={classNames(
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent dark:text-neutral-200",
brand === "#fff" || brand === "#ffffff" ? "" : ""
)}
data-testid="time">
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{!!seatsPerTimeSlot && (
<p
className={`${
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
? "text-rose-600"
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} text-sm`}>
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
{seatsPerTimeSlot} {t("seats_available")}
</p>
)}
</a>
<Link
href={bookingUrl}
prefetch={false}
className={classNames(
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand dark:text-darkgray-800 mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent",
brand === "#fff" || brand === "#ffffff" ? "" : ""
)}
data-testid="time">
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{!!seatsPerTimeSlot && (
<p
className={`${
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
? "text-rose-600"
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} text-sm`}>
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
{seatsPerTimeSlot} {t("seats_available")}
</p>
)}
</Link>
)}
</div>
@@ -76,7 +76,7 @@ const BookingDescription: FC<Props> = (props) => {
profile={profile}
users={eventType.users}
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
size={10}
size="sm"
truncateAfter={3}
/>
<h2 className="mt-2 break-words text-sm font-medium text-gray-600 dark:text-gray-300">
@@ -356,7 +356,7 @@ function BookingListItem(booking: BookingItemProps) {
<div
title={booking.title}
className={classNames(
"max-w-10/12 sm:max-w-56 text-sm font-medium leading-6 text-neutral-900 md:max-w-full",
"max-w-10/12 sm:max-w-56 text-sm font-medium leading-6 text-gray-900 md:max-w-full",
isCancelled ? "line-through" : ""
)}>
{booking.title}
@@ -55,7 +55,7 @@ export default function CancelBooking(props: Props) {
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-4 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white "
className="dark:bg-darkgray-100 dark:border-darkgray-400 mt-2 mb-4 w-full dark:text-white "
rows={3}
/>
<div className="flex flex-col-reverse rtl:space-x-reverse ">
+2 -4
View File
@@ -1,6 +1,5 @@
import { CAL_URL } from "@calcom/lib/constants";
import AvatarGroup, { AvatarGroupProps } from "@components/ui/AvatarGroup";
import { AvatarGroup, AvatarGroupProps } from "@calcom/ui";
export const UserAvatars = ({
profile,
@@ -14,7 +13,6 @@ export const UserAvatars = ({
const showMembers = !users.find((user) => user.name === profile.name) && props.showMembers;
return (
<AvatarGroup
border="border-2 dark:border-darkgray-100 border-white"
items={
[
{
@@ -38,7 +36,7 @@ export const UserAvatars = ({
href?: string;
}[]
}
size={props.size}
size="sm"
truncateAfter={props.truncateAfter}
/>
);
@@ -28,7 +28,7 @@ import { getRecurringFreq } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Icon, HeadSeo } from "@calcom/ui";
import { timeZone as localStorageTimeZone } from "@lib/clock";
import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -37,7 +37,6 @@ import Gates, { Gate, GateState } from "@components/Gates";
import AvailableTimes from "@components/booking/AvailableTimes";
import BookingDescription from "@components/booking/BookingDescription";
import TimeOptions from "@components/booking/TimeOptions";
import { HeadSeo } from "@components/seo/head-seo";
import PoweredByCal from "@components/ui/PoweredByCal";
import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
@@ -109,7 +109,7 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
<div className="space-y-4">
<div>
<div className="min-w-4 mb-2">
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-neutral-700">
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-gray-700">
{t("availability")}
</label>
</div>
@@ -93,10 +93,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<div className="flex flex-col">
<div className="flex justify-between">
<Label>{t("add_to_calendar")}</Label>
<Link href="/apps/categories/calendar">
<a target="_blank" className="text-sm text-gray-600 hover:text-gray-900">
{t("add_another_calendar")}
</a>
<Link
href="/apps/categories/calendar"
target="_blank"
className="text-sm text-gray-600 hover:text-gray-900">
{t("add_another_calendar")}
</Link>
</div>
<div className="-mt-1 w-full">
@@ -266,12 +267,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
/>
{/*TODO: Extract it out into a component when used more than once*/}
{!IS_SELF_HOSTED && (
<Link href="https://cal.com/pricing">
<a target="_blank">
<Badge variant="orange" className="ml-2">
Platform Only
</Badge>
</a>
<Link href="https://cal.com/pricing" target="_blank">
<Badge variant="orange" className="ml-2">
Platform Only
</Badge>
</Link>
)}
</div>
+95 -113
View File
@@ -1,8 +1,8 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo, useRef, useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import React, { useEffect, useState } from "react";
import { Controller, useFormContext, UseFormRegisterReturn, useWatch } from "react-hook-form";
import { classNames } from "@calcom/lib";
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
@@ -12,13 +12,95 @@ 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";
const MinimumBookingNoticeInput = React.forwardRef<
HTMLInputElement,
Omit<UseFormRegisterReturn<"minimumBookingNotice">, "ref">
>(function MinimumBookingNoticeInput({ ...passThroughProps }, ref) {
const { t } = useLocale();
const { setValue, getValues } = useFormContext<FormValues>();
const durationTypeOptions: {
value: DurationType;
label: string;
}[] = [
{
label: t("minutes"),
value: "minutes",
},
{
label: t("hours"),
value: "hours",
},
{
label: t("days"),
value: "days",
},
];
const [minimumBookingNoticeDisplayValues, setMinimumBookingNoticeDisplayValues] = useState<{
type: DurationType;
value: number;
}>({
type: findDurationType(getValues(passThroughProps.name)),
value: convertToNewDurationType(
"minutes",
findDurationType(getValues(passThroughProps.name)),
getValues(passThroughProps.name)
),
});
// keep hidden field in sync with minimumBookingNoticeDisplayValues
useEffect(() => {
setValue(
passThroughProps.name,
convertToNewDurationType(
minimumBookingNoticeDisplayValues.type,
"minutes",
minimumBookingNoticeDisplayValues.value
)
);
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
return (
<div className="flex items-end justify-end">
<div className="w-1/2 md:w-full">
<InputField
required
defaultValue={minimumBookingNoticeDisplayValues.value}
onChange={(e) =>
setMinimumBookingNoticeDisplayValues({
...minimumBookingNoticeDisplayValues,
value: parseInt(e.target.value || "0", 10),
})
}
label={t("minimum_booking_notice")}
type="number"
placeholder="0"
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
/>
<input type="hidden" ref={ref} {...passThroughProps} />
</div>
<Select
isSearchable={false}
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
defaultValue={durationTypeOptions.find(
(option) => option.value === minimumBookingNoticeDisplayValues.type
)}
onChange={(input) => {
if (input) {
setMinimumBookingNoticeDisplayValues({
...minimumBookingNoticeDisplayValues,
type: input.value,
});
}
}}
options={durationTypeOptions}
/>
</div>
);
});
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
const { t } = useLocale();
const formMethods = useFormContext<FormValues>();
const minimumBookingNoticeType = useRef<DurationType>(findDurationType(eventType.minimumBookingNotice));
const prevBookingNoticeType = useRef<DurationType>(minimumBookingNoticeType.current);
const minimumBookingNoticeInDurationTypeFormValue = formMethods.watch("minimumBookingNoticeInDurationType");
const PERIOD_TYPES = [
{
@@ -49,59 +131,8 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
defaultValue: periodType?.type,
});
const onMinimumNoticeDurationTypeChange = useMemo(
() => (durationType?: DurationType) => {
if (typeof durationType === "undefined") return;
// Store current selected type in ref to use in previous run for comparrison.
minimumBookingNoticeType.current = durationType;
// Uses the previous selected type and converts the value to the new type.
// Eg day was selected before, user selects hours now, so we multiple * 24 and round up.
const minimumBookingNoticeInDurationType = convertToNewDurationType(
prevBookingNoticeType.current,
durationType,
Number(minimumBookingNoticeInDurationTypeFormValue)
);
// Uses the round up value in the new type to calculate the value in minutes.
// We should NOT use minimumBookingNoticeInDurationTypeFormValue here, since that
// would break in the case that the user enters 90 minutes, changes the type to
// hours (that will show 2 hours), but the value in minutes would then remain 90,
// which is something else than the user things they save.
const minimumBookingNoticeInMinutes = convertToNewDurationType(
durationType,
"minutes",
Number(minimumBookingNoticeInDurationType)
);
// Updates both form values as well as stores the current type as previous type in ref.
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
formMethods.setValue("minimumBookingNoticeInDurationType", minimumBookingNoticeInDurationType);
prevBookingNoticeType.current = durationType;
},
[formMethods, minimumBookingNoticeInDurationTypeFormValue]
);
/**
* When the user inputs a new value for minimumBookingNoticeInDurationType,
* we calculate the value in minutes, and update this hidden field as well.
*/
const onMinimumNoticeChange = useMemo(
() => (duration: string) => {
const minimumBookingNoticeInMinutes = convertToNewDurationType(
minimumBookingNoticeType.current,
"minutes",
Number(duration)
);
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
},
[formMethods]
);
return (
<div>
<div className="space-y-8">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="w-full">
<Label htmlFor="beforeBufferTime">{t("before_event")} </Label>
@@ -168,56 +199,10 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/>
</div>
</div>
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="flex w-full items-end justify-end">
<Controller
name="minimumBookingNotice"
control={formMethods.control}
render={() => {
const durationTypeOptions = [
{
label: t("minutes"),
value: "minutes",
},
{
label: t("hours"),
value: "hours",
},
{
label: t("days"),
value: "days",
},
];
return (
<>
<div className="w-1/2 md:w-3/4">
<InputField
required
label={t("minimum_booking_notice")}
type="number"
placeholder="120"
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
{...formMethods.register("minimumBookingNoticeInDurationType", {
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
onMinimumNoticeChange(event.target.value),
})}
/>
<input type="hidden" {...formMethods.register("minimumBookingNotice")} />
</div>
<Select
isSearchable={false}
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
defaultValue={durationTypeOptions.find(
(option) => option.value === minimumBookingNoticeType.current
)}
onChange={(input) => onMinimumNoticeDurationTypeChange(input?.value as DurationType)}
options={durationTypeOptions}
/>
</>
);
}}
/>
<div className="flex flex-col lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="w-full">
<Label htmlFor="minimumBookingNotice">{t("minimum_booking_notice")} </Label>
<MinimumBookingNoticeInput {...formMethods.register("minimumBookingNotice")} />
</div>
<div className="w-full">
<Label htmlFor="slotInterval">{t("slot_interval")} </Label>
@@ -252,9 +237,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
/>
</div>
</div>
<hr className="my-8" />
<hr />
<Controller
name="bookingLimits"
control={formMethods.control}
@@ -276,8 +259,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
</SettingsToggle>
)}
/>
<hr className="my-8" />
<hr />
<Controller
name="periodType"
control={formMethods.control}
@@ -1,16 +1,18 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { MultiValue } from "react-select";
import { z } from "zod";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, Skeleton, TextField, SettingsToggle } from "@calcom/ui";
import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { slugify } from "@lib/slugify";
@@ -195,6 +197,23 @@ export const EventSetupTab = (
</li>
);
})}
{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" />
<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.
Change it{" "}
<Link
href={`${CAL_URL}/event-types/${eventType.id}?tabName=advanced`}
className="underline">
here.
</Link>{" "}
We will fall back to Cal video if you do not change it.
</p>
</Trans>
</div>
)}
{validLocations.length > 0 && validLocations.length !== locationOptions.length && (
<li>
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
@@ -326,6 +345,7 @@ export const EventSetupTab = (
<Skeleton as={Label} loadingClassName="w-16">
{t("location")}
</Skeleton>
<Controller
name="locations"
control={formMethods.control}
+135 -36
View File
@@ -1,9 +1,12 @@
import { SchedulingType } from "@prisma/client/";
import { SchedulingType } from "@prisma/client";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { ComponentProps } from "react";
import { Controller, useFormContext, useWatch, Control } from "react-hook-form";
import { Options } from "react-select";
import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTeamSelect";
import CheckedTeamSelect, {
CheckedSelectOption,
} from "@calcom/features/eventtypes/components/CheckedTeamSelect";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Label, Select } from "@calcom/ui";
@@ -22,14 +25,66 @@ const mapUserToValue = ({ id, name, username, email }: IMemberToValue) => ({
email,
});
export const EventTeamTab = ({
eventType,
team,
teamMembers,
}: Pick<EventTypeSetupProps, "eventType" | "teamMembers" | "team">) => {
const FixedHosts = ({
control,
labelText,
placeholder,
options = [],
...rest
}: {
control: Control<FormValues>;
labelText: string;
placeholder: string;
options?: Options<CheckedSelectOption>;
} & Partial<ComponentProps<typeof CheckedTeamSelect>>) => {
return (
<div className="flex flex-col space-y-5 bg-gray-50 p-4">
<div>
<Label>{labelText}</Label>
<Controller
name="hostsFixed"
control={control}
render={({ field: { onChange, value } }) => {
return (
<CheckedTeamSelect
isDisabled={false}
onChange={(options) => {
onChange(
options.map((option) => ({
isFixed: true,
userId: parseInt(option.value, 10),
}))
);
}}
value={value
.map(
(host) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
options.find((member) => member.value === host.userId.toString())!
)
.filter(Boolean)}
controlShouldRenderValue={false}
options={options}
placeholder={placeholder}
{...rest}
/>
);
}}
/>
</div>
</div>
);
};
export const EventTeamTab = ({ team, teamMembers }: Pick<EventTypeSetupProps, "teamMembers" | "team">) => {
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
const schedulingType = useWatch({
control: formMethods.control,
name: "schedulingType",
});
const schedulingTypeOptions: {
value: SchedulingType;
label: string;
@@ -47,14 +102,13 @@ export const EventTeamTab = ({
},
];
const teamMembersToValues = useMemo(() => {
return teamMembers.map(mapUserToValue);
}, [teamMembers]);
const teamMembersOptions = teamMembers.map(mapUserToValue);
return (
<div>
{team && (
<div className="space-y-3">
<div className="flex flex-col pb-8">
<div className="space-y-5">
<div className="flex flex-col">
<Label>{t("scheduling_type")}</Label>
<Controller
name="schedulingType"
@@ -72,30 +126,75 @@ export const EventTeamTab = ({
/>
</div>
<div className="flex flex-col">
<Label>{t("team")}</Label>
<Controller
name="users"
{schedulingType === SchedulingType.COLLECTIVE && (
<FixedHosts
options={teamMembersOptions}
placeholder={t("add_attendees")}
labelText={t("team")}
control={formMethods.control}
defaultValue={eventType.users.map((user) => user.id.toString())}
render={({ field: { onChange, value } }) => (
<CheckedTeamSelect
isDisabled={false}
onChange={(options) => onChange(options.map((user) => user.value))}
value={value
.map(
(userId) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
teamMembers.map(mapUserToValue).find((member) => member.value === userId)!
)
.filter(Boolean)}
controlShouldRenderValue={false}
options={teamMembersToValues}
placeholder={t("add_attendees")}
/>
)}
/>
</div>
)}
{schedulingType === SchedulingType.ROUND_ROBIN && (
<>
<FixedHosts
options={teamMembersOptions}
isOptionDisabled={(option) =>
!!formMethods.getValues("hosts").find((host) => host.userId.toString() === option.value)
}
placeholder={t("add_fixed_hosts")}
labelText={t("fixed_hosts")}
control={formMethods.control}
/>
<div className="flex flex-col space-y-5 bg-gray-50 p-4">
<div>
<Label>{t("round_robin_hosts")}</Label>
<Controller
name="hosts"
control={formMethods.control}
render={({ field: { onChange, value } }) => (
<CheckedTeamSelect
isDisabled={false}
onChange={(options) =>
onChange(
options.map((option) => ({
isFixed: false,
userId: parseInt(option.value, 10),
}))
)
}
value={value
.map(
(host) =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
teamMembers
.map(mapUserToValue)
.find((member) => member.value === host.userId.toString())!
)
.filter(Boolean)}
controlShouldRenderValue={false}
options={teamMembers.map(mapUserToValue)}
isOptionDisabled={(option) =>
!!formMethods
.getValues("hostsFixed")
.find((host) => host.userId.toString() === option.value)
}
placeholder={t("add_attendees")}
/>
)}
/>
</div>
{/*<TextField
required
type="number"
label={t("minimum_round_robin_hosts_count")}
defaultValue={1}
{...formMethods.register("minimumHostCount")}
addOnSuffix={<>{t("hosts")}</>}
/>*/}
</div>
</>
)}
</div>
)}
</div>
@@ -1,10 +1,11 @@
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo, useState } from "react";
import { useMemo, useState, Suspense } from "react";
import { UseFormReturn } from "react-hook-form";
import { TbWebhook } from "react-icons/tb";
import Shell from "@calcom/features/shell/Shell";
import { classNames } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -15,15 +16,15 @@ import {
ButtonGroup,
ConfirmationDialogContent,
Dialog,
Divider,
DropdownMenuSeparator,
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownItem,
DropdownMenuTrigger,
HorizontalTabs,
Icon,
Label,
Shell,
showToast,
Skeleton,
Switch,
@@ -32,7 +33,6 @@ import {
VerticalTabs,
} from "@calcom/ui";
import { ClientSuspense } from "@components/ClientSuspense";
import { EmbedButton, EmbedDialog } from "@components/Embed";
type Props = {
@@ -56,12 +56,15 @@ function getNavigation(props: {
installedAppsNumber: number;
}) {
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
const duration =
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
return [
{
name: "event_setup_tab_title",
href: `/event-types/${eventType.id}?tabName=setup`,
icon: Icon.FiLink,
info: `${eventType.length} Mins`, // TODO: Get this from props
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
},
{
name: "availability",
@@ -152,7 +155,7 @@ function EventTypeSingleLayout({
// If there is a team put this navigation item within the tabs
if (team) {
navigation.splice(2, 0, {
name: "scheduling_type",
name: "assignment",
href: `/event-types/${eventType.id}?tabName=team`,
icon: Icon.FiUsers,
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
@@ -247,32 +250,36 @@ function EventTypeSingleLayout({
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem className="focus:ring-gray-100">
<Button
color="minimal"
StartIcon={Icon.FiExternalLink}
<DropdownItem
target="_blank"
type="button"
StartIcon={Icon.FiExternalLink}
href={permalink}
rel="noreferrer"
className="min-w-full">
rel="noreferrer">
{t("preview")}
</Button>
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="focus:ring-gray-100">
<Button color="minimal" StartIcon={Icon.FiLink}>
<DropdownItem
type="button"
StartIcon={Icon.FiLink}
onClick={() => {
navigator.clipboard.writeText(permalink);
showToast("Link copied!", "success");
}}>
{t("copy_link")}
</Button>
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="focus:ring-gray-100">
<Button
color="minimal"
<DropdownItem
type="button"
StartIcon={Icon.FiTrash}
disabled={!hasPermsToDelete}
onClick={() => setDeleteDialogOpen(true)}
className="min-w-full">
onClick={() => setDeleteDialogOpen(true)}>
{t("delete")}
</Button>
</DropdownItem>
</DropdownMenuItem>
<Divider />
<DropdownMenuSeparator className="block sm:hidden" />
<div className="flex items-center rounded-md py-1.5 px-4 sm:hidden sm:hover:bg-gray-100">
<Skeleton
as={Label}
@@ -301,7 +308,7 @@ function EventTypeSingleLayout({
</Button>
</div>
}>
<ClientSuspense fallback={<Icon.FiLoader />}>
<Suspense fallback={<Icon.FiLoader />}>
<div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
<div className="hidden xl:block">
<VerticalTabs
@@ -324,7 +331,7 @@ function EventTypeSingleLayout({
</div>
</div>
</div>
</ClientSuspense>
</Suspense>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
isLoading={deleteMutation.isLoading}
@@ -57,7 +57,7 @@ export default function RecurringEventController({
{recurringEventState && (
<div data-testid="recurring-event-collapsible" className="text-sm">
<div className="flex items-center">
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("repeats_every")}</p>
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">{t("repeats_every")}</p>
<input
type="number"
min="1"
@@ -89,7 +89,7 @@ export default function RecurringEventController({
/>
</div>
<div className="mt-4 flex items-center">
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("for_a_maximum_of")}</p>
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">{t("for_a_maximum_of")}</p>
<input
type="number"
min="1"
@@ -105,7 +105,7 @@ export default function RecurringEventController({
setRecurringEventState(newVal);
}}
/>
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">
{t("events", {
count: recurringEventState.count,
})}
@@ -16,7 +16,7 @@ const CreateEventsOnCalendarSelect = (props: ICreateEventsOnCalendarSelectProps)
<>
<div className="mt-6 flex flex-row">
<div className="w-full">
<label htmlFor="createEventsOn" className="flex text-sm font-medium text-neutral-700">
<label htmlFor="createEventsOn" className="flex text-sm font-medium text-gray-700">
{t("create_events_on")}
</label>
<div className="mt-2">
@@ -19,7 +19,8 @@ interface IUserSettingsProps {
const UserSettings = (props: IUserSettingsProps) => {
const { user, nextStep } = props;
const { t } = useLocale();
const [selectedTimeZone, setSelectedTimeZone] = useState(user.timeZone ?? dayjs.tz.guess());
const [selectedTimeZone, setSelectedTimeZone] = useState(dayjs.tz.guess());
const {
register,
handleSubmit,
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { Toaster } from "react-hot-toast";
import { StepCard, Steps } from "../../..";
import { StepCard, Steps } from "@calcom/ui";
export default function WizardLayout({
children,
+4
View File
@@ -0,0 +1,4 @@
We don't have many layouts currently however, if they are relevant to a features - please put them in their relvant component folder
in `web/components` if you believe they will be reused in other apps please place them in `packages/features/[feature]`
Thanks Sean 🔥
+2 -2
View File
@@ -22,8 +22,8 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
/>
<section className="line-clamp-4 mt-2 w-full space-y-1">
<p className="font-medium text-neutral-900 dark:text-white">{member.name}</p>
<p className="line-clamp-3 overflow-ellipsis text-sm font-normal text-neutral-500 dark:text-white">
<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>
</section>
+3 -5
View File
@@ -1,11 +1,9 @@
import classNames from "classnames";
import { APP_NAME, LOGO } from "@calcom/lib/constants";
import { Credits, HeadSeo } from "@calcom/ui";
import Loader from "@components/Loader";
import { HeadSeo } from "@components/seo/head-seo";
import Credits from "../../../../packages/ui/v2/core/Credits";
interface Props {
title: string;
@@ -26,7 +24,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
)}
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
{props.heading && <h2 className="font-cal text-center text-3xl text-neutral-900">{props.heading}</h2>}
{props.heading && <h2 className="font-cal text-center text-3xl text-gray-900">{props.heading}</h2>}
</div>
{props.loading && (
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-50">
@@ -37,7 +35,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
<div className="border-1 mx-2 rounded-md border-gray-200 bg-white px-4 py-10 sm:px-10">
{props.children}
</div>
<div className="mt-8 text-center text-sm text-neutral-600">
<div className="mt-8 text-center text-sm text-gray-600">
{props.footerText}
<Credits />
</div>
+1 -7
View File
@@ -35,13 +35,7 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
return (
<li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2">
{item.href ? (
<Link href={item.href}>
<a>{avatar}</a>
</Link>
) : (
avatar
)}
{item.href ? <Link href={item.href}>{avatar}</Link> : avatar}
</li>
);
}
+1 -1
View File
@@ -13,7 +13,7 @@ export default function LinkIconButton(props: LinkIconButtonProps) {
type="button"
{...props}
className="text-md flex items-center rounded-sm px-2 py-1 text-sm font-medium text-gray-700 hover:bg-gray-200 hover:text-gray-900">
<props.Icon className="h-4 w-4 text-neutral-500 ltr:mr-2 rtl:ml-2" />
<props.Icon className="h-4 w-4 text-gray-500 ltr:mr-2 rtl:ml-2" />
{props.children}
</button>
</div>
-22
View File
@@ -1,22 +0,0 @@
import Link from "next/link";
interface ILinkTextProps {
href: string;
children: React.ReactNode;
classNameChildren?: string;
target?: string;
}
/**
* This component had to be made in order to make i18n work with next/link
* @see https://github.com/i18next/react-i18next/issues/1090#issuecomment-615426145
**/
export const LinkText = (props: ILinkTextProps) => {
const { target, href, children, classNameChildren, ...moreProps } = props;
return (
<Link href={href || ""} {...moreProps}>
<a target={target || ""} className={classNameChildren}>
{children}
</a>
</Link>
);
};
+15 -20
View File
@@ -9,26 +9,21 @@ const PoweredByCal = () => {
const isEmbed = useIsEmbed();
return (
<div className={"p-2 text-center text-xs sm:text-right" + (isEmbed ? " max-w-3xl" : "")}>
<Link href={POWERED_BY_URL}>
<a target="_blank" className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
{t("powered_by")}{" "}
{
// eslint-disable-next-line @next/next/no-img-element
<img
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="/cal-logo-word.svg"
alt="Cal.com Logo"
/>
}
{
// eslint-disable-next-line @next/next/no-img-element
<img
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
src="/cal-logo-word-dark.svg"
alt="Cal.com Logo"
/>
}
</a>
<Link
href={POWERED_BY_URL}
target="_blank"
className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
{t("powered_by")}{" "}
<img
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="/cal-logo-word.svg"
alt="Cal.com Logo"
/>
<img
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
src="/cal-logo-word-dark.svg"
alt="Cal.com Logo"
/>
</Link>
</div>
);
@@ -13,8 +13,8 @@ export default function SettingInputContainer({
<div className="space-y-3">
<div className="block sm:flex">
<div className="min-w-48 mb-4 sm:mb-0">
<label htmlFor={htmlFor} className="mt-1 flex text-sm font-medium text-neutral-700">
<Icon className="mt-0.5 h-4 w-4 text-neutral-500 ltr:mr-2 rtl:ml-2" />
<label htmlFor={htmlFor} className="mt-1 flex text-sm font-medium text-gray-700">
<Icon className="mt-0.5 h-4 w-4 text-gray-500 ltr:mr-2 rtl:ml-2" />
{label}
</label>
</div>
@@ -187,7 +187,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<div className="flex justify-items-center">
<Label htmlFor="username">{t("username")}</Label>
</div>
<div className="mt-2 flex rounded-md">
<div className="flex rounded-md">
<span
className={classNames(
isInputUsernamePremium ? "border-1 border-orange-400 " : "",
@@ -118,7 +118,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
return (
<div>
<div className="mt-2 flex rounded-md">
<div className="flex rounded-md">
<div className="relative w-full">
<TextField
ref={usernameRef}
@@ -21,7 +21,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
{React.createElement(
descriptionAsLabel ? "div" : "label",
{
className: "flex text-sm font-medium text-neutral-700",
className: "flex text-sm font-medium text-gray-700",
...(!descriptionAsLabel
? {
htmlFor: rest.id,
@@ -39,7 +39,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
{
className: classNames(
"relative flex items-start",
descriptionAsLabel ? "text-neutral-700" : "text-neutral-900"
descriptionAsLabel ? "text-gray-700" : "text-gray-900"
),
},
<>
@@ -50,7 +50,7 @@ export const CheckedSelect = ({
{option.label}
<Icon.FiX
onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-neutral-500"
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-gray-500"
/>
</div>
))}
+1 -1
View File
@@ -10,7 +10,7 @@ const MinutesField = forwardRef<HTMLInputElement, Props>(({ label, ...rest }, re
<div className="block sm:flex">
{!!label && (
<div className="min-w-48 mb-4 sm:mb-0">
<label htmlFor={rest.id} className="flex h-full items-center text-sm font-medium text-neutral-700">
<label htmlFor={rest.id} className="flex h-full items-center text-sm font-medium text-gray-700">
{label}
</label>
</div>
@@ -1,79 +0,0 @@
import React, { ReactNode, useState } from "react";
import classNames from "@lib/classNames";
type RadioAreaProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange"> & {
onChange?: (value: string) => void;
defaultChecked?: boolean;
};
const RadioArea = (props: RadioAreaProps) => {
return (
<label
className={classNames(
"border-1 block border p-4 focus:outline-none focus:ring focus:ring-neutral-500",
props.checked && "border-brand",
props.className
)}>
<input
onChange={(e) => {
if (typeof props.onChange === "function") {
props.onChange(e.target.value);
}
}}
checked={props.checked}
className="float-right ml-3 text-neutral-900 focus:ring-neutral-500"
name={props.name}
value={props.value}
type="radio"
/>
{props.children}
</label>
);
};
type ChildrenProps = {
props: RadioAreaProps;
children?: ReactNode;
};
interface RadioAreaGroupProps extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
children: ChildrenProps | ChildrenProps[];
name?: string;
onChange?: (value: string) => void;
}
const RadioAreaGroup = ({ children, name, onChange, ...passThroughProps }: RadioAreaGroupProps) => {
const [checkedIdx, setCheckedIdx] = useState<number | null>(null);
const changeHandler = (value: string, idx: number) => {
if (onChange) {
onChange(value);
}
setCheckedIdx(idx);
};
return (
<div {...passThroughProps}>
{(Array.isArray(children) ? children : [children]).map((child, idx: number) => {
if (checkedIdx === null && child.props.defaultChecked) {
setCheckedIdx(idx);
}
return (
<Item
{...child.props}
key={idx}
name={name}
checked={idx === checkedIdx}
onChange={(value: string) => changeHandler(value, idx)}>
{child.props.children}
</Item>
);
})}
</div>
);
};
const Item = RadioArea;
const Group = RadioAreaGroup;
export { RadioArea, RadioAreaGroup, Item, Group };
@@ -1,61 +0,0 @@
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import React from "react";
import { Icon } from "@calcom/ui";
import classNames from "@lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
import { RadioArea, RadioAreaGroup } from "@components/ui/form/radio-area/RadioAreaGroup";
interface OptionProps
extends Pick<React.OptionHTMLAttributes<HTMLOptionElement>, "value" | "label" | "className"> {
description?: string;
}
interface RadioAreaSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "onChange"> {
options: OptionProps[]; // allow options to be passed programmatically, like options={}
onChange?: (value: string) => void;
}
export const Select = function RadioAreaSelect(props: RadioAreaSelectProps) {
const { t } = useLocale();
const {
options,
disabled = !options.length, // if not explicitly disabled and the options length is empty, disable anyway
placeholder = t("select"),
} = props;
const getLabel = (value: string | ReadonlyArray<string> | number | undefined) =>
options.find((option: OptionProps) => option.value === value)?.label;
return (
<Collapsible className={classNames("w-full", props.className)}>
<CollapsibleTrigger
type="button"
disabled={disabled}
className={classNames(
"border-1 focus:ring-primary-500 mb-1 block w-full cursor-pointer rounded-sm border border-gray-300 bg-white p-2 text-left text-sm",
disabled && "cursor-default bg-gray-200 focus:ring-0 "
)}>
{getLabel(props.value) ?? placeholder}
<Icon.FiChevronDown className="float-right h-5 w-5 text-neutral-500" />
</CollapsibleTrigger>
<CollapsibleContent>
<RadioAreaGroup className="space-y-2 text-sm" name={props.name} onChange={props.onChange}>
{options.map((option) => (
<RadioArea
{...option}
key={Array.isArray(option.value) ? option.value.join(",") : `${option.value}`}
defaultChecked={props.value === option.value}>
<strong className="mb-1 block">{option.label}</strong>
<p>{option.description}</p>
</RadioArea>
))}
</RadioAreaGroup>
</CollapsibleContent>
</Collapsible>
);
};
export default Select;
@@ -1,2 +0,0 @@
export * from "./RadioAreaGroup";
export * from "./Select";
+1
View File
@@ -1,3 +1,4 @@
// lets refactor and move this into packages/lib/hooks/
import { useState, useEffect } from "react";
const useMediaQuery = (query: string) => {
+6
View File
@@ -89,6 +89,12 @@ const nextConfig = {
eslint: {
ignoreDuringBuilds: !!process.env.CI,
},
// TODO: We need to have all components in `@calcom/ui/components` in order to use this
// modularizeImports: {
// "@calcom/ui": {
// transform: "@calcom/ui/components/{{member}}",
// },
// },
images: {
unoptimized: true,
},
+5 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.4.3",
"version": "2.5.0",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -67,6 +67,7 @@
"bcryptjs": "^2.4.3",
"classnames": "^2.3.1",
"dotenv-cli": "^6.0.0",
"eslint-config-next": "^13.1.1",
"googleapis": "^84.0.0",
"gray-matter": "^4.0.3",
"handlebars": "^4.7.7",
@@ -80,16 +81,14 @@
"memory-cache": "^0.2.0",
"micro": "^9.4.1",
"mime-types": "^2.1.35",
"next": "^12.3.1",
"next": "^13.1.1",
"next-auth": "^4.10.3",
"next-axiom": "^0.10.0",
"next-axiom": "^0.16.0",
"next-collect": "^0.2.1",
"next-i18next": "^11.3.0",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.26.0",
"next-themes": "^0.2.0",
"next-transpile-modules": "^9.0.0",
"nock": "^13.2.8",
"next-transpile-modules": "^10.0.0",
"nodemailer": "^6.7.8",
"otplib": "^12.0.1",
"qrcode": "^1.5.1",
+47 -51
View File
@@ -4,12 +4,10 @@ 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 } from "@calcom/ui";
import { Icon, HeadSeo } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
import { HeadSeo } from "@components/seo/head-seo";
import { ssgInit } from "@server/lib/ssg";
export default function Custom404() {
@@ -100,26 +98,26 @@ export default function Custom404() {
<ul role="list" className="divide-y divide-gray-200 border-gray-200">
<li className="px-4 py-2">
<Link href={`${DEVELOPER_DOCS}/self-hosting/installation`}>
<a 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" />
<Link
href={`${DEVELOPER_DOCS}/self-hosting/installation`}
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" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{t("prisma_studio_tip")}
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{t("prisma_studio_tip")}
</span>
</h3>
<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" />
</div>
</a>
</h3>
<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" />
</div>
</Link>
</li>
<li className="px-4 py-2">
@@ -169,11 +167,11 @@ export default function Custom404() {
</li>
</ul>
<div className="mt-8">
<Link href={`${WEBSITE_URL}/enterprise`}>
<a className="text-base font-medium text-black hover:text-gray-500">
{t("contact_sales")}
<span aria-hidden="true"> &rarr;</span>
</a>
<Link
href={`${WEBSITE_URL}/enterprise`}
className="text-base font-medium text-black hover:text-gray-500">
{t("contact_sales")}
<span aria-hidden="true"> &rarr;</span>
</Link>
</div>
</div>
@@ -245,26 +243,26 @@ export default function Custom404() {
<ul role="list" className="mt-4 divide-y divide-gray-200 border-gray-200">
{links.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2">
<Link href={link.href}>
<a 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">
<link.icon className="h-6 w-6 text-gray-700" aria-hidden="true" />
<Link
href={link.href}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<link.icon className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span>
</h3>
<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" />
</div>
</a>
</h3>
<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" />
</div>
</Link>
</li>
))}
@@ -315,11 +313,9 @@ export default function Custom404() {
</li>
</ul>
<div className="mt-8">
<Link href="/">
<a className="text-base font-medium text-black hover:text-gray-500">
{t("or_go_back_home")}
<span aria-hidden="true"> &rarr;</span>
</a>
<Link href="/" className="text-base font-medium text-black hover:text-gray-500">
{t("or_go_back_home")}
<span aria-hidden="true"> &rarr;</span>
</Link>
</div>
</div>
+36 -41
View File
@@ -1,6 +1,5 @@
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
@@ -27,17 +26,15 @@ 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 } from "@calcom/ui";
import { Icon, HeadSeo, AvatarGroup } from "@calcom/ui";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvatarGroup from "@components/ui/AvatarGroup";
import { AvatarSSR } from "@components/ui/AvatarSSR";
import { ssrInit } from "@server/lib/ssr";
const HeadSeo = dynamic(() => import("@components/seo/head-seo"));
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
@@ -61,25 +58,25 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
key={index}
className="dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-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" />
<Link href={getUsernameSlugLink({ users: props.users, slug: type.slug })}>
<a className="flex justify-between px-6 py-4" data-testid="event-type-link">
<div className="flex-shrink">
<p className="dark:text-darkgray-700 text-sm font-semibold text-neutral-900">{type.title}</p>
<EventTypeDescription className="text-sm" eventType={type} />
</div>
<div className="mt-1 self-center">
<AvatarGroup
border="border-2 border-white dark:border-darkgray-100"
truncateAfter={4}
className="flex flex-shrink-0"
size={10}
items={props.users.map((user) => ({
alt: user.name || "",
image: user.avatar || "",
}))}
/>
</div>
</a>
<Link
href={getUsernameSlugLink({ users: props.users, slug: type.slug })}
className="flex justify-between px-6 py-4"
data-testid="event-type-link">
<div className="flex-shrink">
<p className="dark:text-darkgray-700 text-sm font-semibold text-gray-900">{type.title}</p>
<EventTypeDescription className="text-sm" eventType={type} />
</div>
<div className="mt-1 self-center">
<AvatarGroup
truncateAfter={4}
className="flex flex-shrink-0"
size="sm"
items={props.users.map((user) => ({
alt: user.name || "",
image: user.avatar || "",
}))}
/>
</div>
</Link>
</li>
))}
@@ -136,13 +133,13 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
{isSingleUser && ( // When we deal with a single user, not dynamic group
<div className="mb-8 text-center">
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername} />
<h1 className="font-cal mb-1 text-3xl text-neutral-900 dark:text-white">
<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" />
)}
</h1>
<p className="dark:text-darkgray-600 text-s text-neutral-500">{user.bio}</p>
<p className="dark:text-darkgray-600 text-s text-gray-500">{user.bio}</p>
</div>
)}
<div
@@ -176,22 +173,20 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
href={{
pathname: `/${user.username}/${type.slug}`,
query,
}}>
<a
onClick={async () => {
sdkActionManager?.fire("eventTypeSelected", {
eventType: type,
});
}}
className="block w-full p-5"
data-testid="event-type-link">
<div className="flex flex-wrap items-center">
<h2 className="dark:text-darkgray-700 pr-2 text-sm font-semibold text-gray-700">
{type.title}
</h2>
</div>
<EventTypeDescription eventType={type} />
</a>
}}
onClick={async () => {
sdkActionManager?.fire("eventTypeSelected", {
eventType: type,
});
}}
className="block w-full p-5"
data-testid="event-type-link">
<div className="flex flex-wrap items-center">
<h2 className="dark:text-darkgray-700 pr-2 text-sm font-semibold text-gray-700">
{type.title}
</h2>
</div>
<EventTypeDescription eventType={type} />
</Link>
</div>
))
-1
View File
@@ -53,7 +53,6 @@ class MyDocument extends Document<Props> {
return (
<Html lang={locale} dir={dir}>
<Head>
<link rel="preload" href="/cal.ttf" as="font" type="font/ttf" crossOrigin="anonymous" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
+6 -1
View File
@@ -1,10 +1,10 @@
import { IdentityProvider } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";
import { hashPassword } from "@calcom/lib/auth";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma from "@calcom/prisma";
import { hashPassword } from "@lib/auth";
import slugify from "@lib/slugify";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -12,6 +12,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return;
}
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
res.status(403).json({ message: "Signup is disabled" });
return;
}
const data = req.body;
const { email, password } = data;
const username = slugify(data.username);
@@ -37,6 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
location: true,
user: {
select: {
id: true,
email: true,
name: true,
username: true,
@@ -100,6 +101,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {
id: user.id,
email: user.email,
name,
timeZone: user.timeZone,
+20 -2
View File
@@ -35,6 +35,24 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
uid: "xxyPr4cg2xx4XoS2KeMEQy",
metadata: {},
recurringEvent: null,
appsStatus: [
{
appName: "Outlook Calendar",
type: "office365_calendar",
success: 1,
failures: 0,
errors: [],
warnings: [],
},
{
appName: "Google Meet",
type: "conferencing",
success: 0,
failures: 1,
errors: [],
warnings: ["In order to use Google Meet you must set your destination calendar to a Google Calendar"],
},
],
};
req.statusCode = 200;
@@ -42,9 +60,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
renderEmail("OrganizerRequestEmail", {
attendee: evt.attendees[0],
renderEmail("OrganizerScheduledEmail", {
calEvent: evt,
attendee: evt.organizer,
})
);
res.end();
+1 -1
View File
@@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from "next";
import type { Session } from "next-auth";
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
import { getSession } from "@calcom/lib/auth";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import prisma from "@calcom/prisma";
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";
const defaultIntegrationAddHandler = async ({
+13 -25
View File
@@ -1,34 +1,18 @@
import fs from "fs";
import matter from "gray-matter";
import MarkdownIt from "markdown-it";
import { GetStaticPaths, GetStaticPropsContext } from "next";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import Image from "next/image";
import Link from "next/link";
import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import ExisitingGoogleCal from "@calcom/app-store/googlevideo/components/ExistingGoogleCal";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import App from "@components/apps/App";
const components = {
a: ({ href = "", ...otherProps }: JSX.IntrinsicElements["a"]) => (
<Link href={href}>
<a {...otherProps} />
</Link>
),
img: ({ src = "", alt = "", placeholder, ...rest }: JSX.IntrinsicElements["img"]) => (
<Image src={src} alt={alt} {...rest} />
),
// @TODO: In v2 the slider isn't shown anymore. However, to ensure the v1 pages keep
// working, this component is still rendered in the MDX content. To skip them in the v2
// content we have to render null here. In v2 the gallery is shown by directly
// using the `items` property from the MDX's meta data.
Slider: () => <></>,
};
const md = new MarkdownIt("default", { html: true, breaks: true });
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
@@ -50,10 +34,15 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
email={data.email}
licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly}
images={source?.scope?.items as string[] | undefined}
images={source.data?.items as string[] | undefined}
// tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy"
body={<MDXRemote {...source} components={components} />}
body={
<>
{data.slug === "google-meet" && <ExisitingGoogleCal />}
<div dangerouslySetInnerHTML={{ __html: md.render(source.content) }} />
</>
}
/>
);
}
@@ -82,7 +71,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (!singleApp) return { notFound: true };
const appDirname = app.dirName;
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/README.mdx`);
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
const postFilePath = path.join(README_PATH);
let source = "";
@@ -90,16 +79,15 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
/* If the app doesn't have a README we fallback to the package description */
source = fs.readFileSync(postFilePath).toString();
} catch (error) {
console.log(`No README.mdx provided for: ${appDirname}`);
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
source = singleApp.description;
}
const { content, data } = matter(source);
const mdxSource = await serialize(content, { scope: data });
return {
props: {
source: mdxSource,
source: { content, data },
data: singleApp,
},
};
@@ -4,9 +4,10 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { AppCard, Shell, SkeletonText } from "@calcom/ui";
import { AppCard, SkeletonText } from "@calcom/ui";
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t, isLocaleReady } = useLocale();
@@ -20,10 +21,10 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
backPath="/apps"
heading={
<>
<Link href="/apps">
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
{isLocaleReady ? t("app_store") : <SkeletonText className="h-4 w-24" />}{" "}
</a>
<Link
href="/apps"
className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
{isLocaleReady ? t("app_store") : <SkeletonText className="h-4 w-24" />}{" "}
</Link>
{category && (
<span className="gap-1 text-gray-600">
+19 -18
View File
@@ -2,8 +2,9 @@ import { InferGetStaticPropsType } from "next";
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, Shell, SkeletonText } from "@calcom/ui";
import { Icon, SkeletonText } from "@calcom/ui";
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t, isLocaleReady } = useLocale();
@@ -11,28 +12,28 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
return (
<Shell isPublic large>
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
<Link href="/apps">
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
<Icon.FiArrowLeft className="h-4 w-4" />
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
</a>
<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" />
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
</Link>
</div>
<div className="mb-16">
<div className="grid h-auto w-full grid-cols-5 gap-3">
{categories.map((category) => (
<Link key={category.name} href={"/apps/categories/" + category.name}>
<a
data-testid={`app-store-category-${category.name}`}
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
<div className="self-center">
<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" />
</p>
</div>
</a>
<Link
key={category.name}
href={"/apps/categories/" + category.name}
data-testid={`app-store-category-${category.name}`}
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
<div className="self-center">
<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" />
</p>
</div>
</Link>
))}
</div>
+44 -13
View File
@@ -7,10 +7,31 @@ import { getSession } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { AppCategories } from "@calcom/prisma/client";
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { AllApps, AppsLayout, AppStoreCategories, Icon, TextField, TrendingAppsSlider } from "@calcom/ui";
import {
AllApps,
AppStoreCategories,
HorizontalTabItemProps,
HorizontalTabs,
Icon,
TextField,
TrendingAppsSlider,
} from "@calcom/ui";
import AppsLayout from "@components/apps/layouts/AppsLayout";
import { ssgInit } from "@server/lib/ssg";
const tabs: HorizontalTabItemProps[] = [
{
name: "app_store",
href: "/apps",
},
{
name: "installed_apps",
href: "/apps/installed",
},
];
function AppsSearch({
onChange,
className,
@@ -41,20 +62,30 @@ export default function Apps({ categories, appStore }: inferSSRProps<typeof getS
heading={t("app_store")}
subtitle={t("app_store_description")}
actions={(className) => (
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
<div className="flex w-full flex-col md:flex-row md:justify-between lg:w-auto">
<div className="lg:hidden">
<HorizontalTabs tabs={tabs} />
</div>
<div>
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
</div>
</div>
)}
headerClassName="sm:hidden lg:block hidden"
emptyStore={!appStore.length}>
{!searchText && (
<>
<AppStoreCategories categories={categories} />
<TrendingAppsSlider items={appStore} />
</>
)}
<AllApps
apps={appStore}
searchText={searchText}
categories={categories.map((category) => category.name)}
/>
<div className="flex flex-col gap-y-8">
{!searchText && (
<>
<AppStoreCategories categories={categories} />
<TrendingAppsSlider items={appStore} />
</>
)}
<AllApps
apps={appStore}
searchText={searchText}
categories={categories.map((category) => category.name)}
/>
</div>
</AppsLayout>
);
}
+24 -23
View File
@@ -14,16 +14,16 @@ import {
Button,
EmptyScreen,
Icon,
InstalledAppsLayout,
List,
ShellSubHeading,
AppSkeletonLoader as SkeletonLoader,
ShellSubHeading,
} from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
import IntegrationListItem from "@components/apps/IntegrationListItem";
import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout";
function ConnectOrDisconnectIntegrationButton(props: {
credentialIds: number[];
@@ -32,7 +32,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
installed?: boolean;
invalidCredentialIds?: number[];
}) {
const { type, credentialIds, isGlobal, installed, invalidCredentialIds } = props;
const { type, credentialIds, isGlobal, installed } = props;
const { t } = useLocale();
const [credentialId] = credentialIds;
@@ -92,8 +92,8 @@ function ConnectOrDisconnectIntegrationButton(props: {
}
interface IntegrationsContainerProps {
variant?: keyof typeof InstalledAppVariants;
exclude?: (keyof typeof InstalledAppVariants)[];
variant?: typeof InstalledAppVariants[number];
exclude?: typeof InstalledAppVariants[number][];
}
interface IntegrationsListProps {
@@ -143,8 +143,10 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
automation: Icon.FiShare2,
analytics: Icon.FiBarChart,
payment: Icon.FiCreditCard,
web3: Icon.FiBarChart,
other: Icon.FiGrid,
};
return (
<QueryCell
query={query}
@@ -183,6 +185,7 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
buttonRaw={
<Button
color="secondary"
data-testid={`connect-${variant || "other"}-apps`}
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
{t(`connect_${variant || "other"}_apps`)}
</Button>
@@ -197,31 +200,29 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
};
const querySchema = z.object({
category: z.nativeEnum(InstalledAppVariants),
category: z.enum(InstalledAppVariants),
});
type querySchemaType = z.infer<typeof querySchema>;
export default function InstalledApps() {
const { t } = useLocale();
const router = useRouter();
const category = router.query.category;
const category = router.query.category as querySchemaType["category"];
const categoryList: querySchemaType["category"][] = [
"payment",
"conferencing",
"automation",
"analytics",
"web3",
];
return (
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
{(category === InstalledAppVariants.payment || category === InstalledAppVariants.conferencing) && (
<IntegrationsContainer variant={category} />
)}
{(category === InstalledAppVariants.automation || category === InstalledAppVariants.analytics) && (
<IntegrationsContainer variant={category} />
)}
{category === InstalledAppVariants.calendar && <CalendarListContainer />}
{category === InstalledAppVariants.other && (
<IntegrationsContainer
exclude={[
InstalledAppVariants.conferencing,
InstalledAppVariants.calendar,
InstalledAppVariants.analytics,
InstalledAppVariants.automation,
]}
/>
{categoryList.includes(category) && <IntegrationsContainer variant={category} />}
{category === "calendar" && <CalendarListContainer />}
{category === "other" && (
<IntegrationsContainer variant={category} exclude={[...categoryList, "calendar"]} />
)}
</InstalledAppsLayout>
);
+1 -1
View File
@@ -40,7 +40,7 @@ export default function Error() {
</div>
</div>
<div className="mt-5 sm:mt-6">
<Link href="/auth/login" passHref>
<Link href="/auth/login" passHref legacyBehavior>
<Button className="flex w-full justify-center">{t("go_back_login")}</Button>
</Link>
</div>
+1 -1
View File
@@ -81,7 +81,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
<h2 className="text-center text-3xl font-extrabold text-gray-900">{t("request_is_expired")}</h2>
</div>
<p>{t("request_is_expired_instructions")}</p>
<Link href="/auth/forgot-password" passHref>
<Link href="/auth/forgot-password" passHref legacyBehavior>
<button
type="button"
className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
@@ -94,8 +94,8 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
footerText={
!success && (
<>
<Link href="/auth/login">
<a className="font-medium text-neutral-900">{t("back_to_signin")}</a>
<Link href="/auth/login" className="font-medium text-gray-900">
{t("back_to_signin")}
</Link>
</>
)
+12 -5
View File
@@ -115,7 +115,13 @@ export default function Login({
description={t("login")}
showLogo
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
footerText={
twoFactorRequired
? TwoFactorFooter
: process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== "true"
? LoginFooter
: null
}>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form">
<div>
@@ -133,10 +139,11 @@ export default function Login({
/>
<div className="relative">
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot")}
</a>
<Link
href="/auth/forgot-password"
tabIndex={-1}
className="text-sm font-medium text-gray-600">
{t("forgot")}
</Link>
</div>
<PasswordField
+1 -1
View File
@@ -27,7 +27,7 @@ export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
{
title: t("enable_apps"),
description: t("enable_apps_description"),
content: <AdminAppsList baseURL="/auth/setup?step=2" />,
content: <AdminAppsList baseURL="/auth/setup?step=2" useQueryParam={true} />,
isLoading: false,
},
];
+2 -2
View File
@@ -6,7 +6,7 @@ import * as React from "react";
import { useEffect, useState, useRef } from "react";
import z from "zod";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { trpc } from "@calcom/trpc/react";
import { Button, showToast } from "@calcom/ui";
@@ -110,7 +110,7 @@ export default function Verify() {
? "Your payment failed"
: sessionId
? "Payment successful!"
: "Verify your email" + " | Cal.com"}
: "Verify your email" + " | " + APP_NAME}
</title>
</Head>
<div className="flex min-h-screen flex-col items-center justify-center px-6">
+2 -2
View File
@@ -4,6 +4,7 @@ import { z } from "zod";
import { DateOverrideInputDialog, DateOverrideList } from "@calcom/features/schedules";
import Schedule from "@calcom/features/schedules/components/Schedule";
import Shell from "@calcom/features/shell/Shell";
import { availabilityAsString } from "@calcom/lib/availability";
import { yyyymmdd } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -16,7 +17,6 @@ import {
Form,
Icon,
Label,
Shell,
showToast,
Skeleton,
SkeletonText,
@@ -60,7 +60,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
</span>
</Tooltip>
</h3>
<p className="mb-4 text-sm text-neutral-500 ltr:mr-4 rtl:ml-4">{t("date_overrides_subtitle")}</p>
<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">
<DateOverrideList
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
+2 -1
View File
@@ -2,9 +2,10 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { GetServerSidePropsContext } from "next";
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, Shell, showToast } from "@calcom/ui";
import { EmptyScreen, Icon, showToast } from "@calcom/ui";
import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
+5 -6
View File
@@ -1,7 +1,8 @@
import dayjs from "@calcom/dayjs";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Shell, SkeletonText } from "@calcom/ui";
import { SkeletonText } from "@calcom/ui";
import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -55,7 +56,7 @@ const AvailabilityView = ({ user }: { user: User }) => {
if (e.target.value) setSelectedDate(e.target.value);
}}
/>
<small className="block text-neutral-400">{t("hover_over_bold_times_tip")}</small>
<small className="block text-gray-400">{t("hover_over_bold_times_tip")}</small>
<div className="mt-4 space-y-4">
<div className="bg-brand dark:bg-darkmodebrand overflow-hidden rounded-md">
<div className="text-brandcontrast dark:text-darkmodebrandcontrast px-4 py-2 sm:px-6">
@@ -81,13 +82,11 @@ const AvailabilityView = ({ user }: { user: User }) => {
data-testid="troubleshooter-busy-time">
<div className="px-4 py-5 text-black sm:p-6">
{t("calendar_shows_busy_between")}{" "}
<span
className="font-medium text-neutral-800"
title={dayjs(slot.start).format("HH:mm")}>
<span className="font-medium text-gray-800" title={dayjs(slot.start).format("HH:mm")}>
{dayjs(slot.start).format("HH:mm")}
</span>{" "}
{t("and")}{" "}
<span className="font-medium text-neutral-800" title={dayjs(slot.end).format("HH:mm")}>
<span className="font-medium text-gray-800" title={dayjs(slot.end).format("HH:mm")}>
{dayjs(slot.end).format("HH:mm")}
</span>{" "}
{t("on")} {dayjs(slot.start).format("D")}{" "}
+95 -96
View File
@@ -32,17 +32,16 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
import { localStorage } from "@calcom/lib/webstorage";
import prisma, { baseUserSelect } from "@calcom/prisma";
import prisma from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Button, EmailInput, Icon } from "@calcom/ui";
import { Button, EmailInput, Icon, HeadSeo } from "@calcom/ui";
import { timeZone } from "@lib/clock";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import CancelBooking from "@components/booking/CancelBooking";
import EventReservationSchema from "@components/schemas/EventReservationSchema";
import { HeadSeo } from "@components/seo/head-seo";
import { ssrInit } from "@server/lib/ssr";
@@ -323,7 +322,7 @@ export default function Success(props: SuccessProps) {
}
return t("emailed_you_and_attendees" + titleSuffix);
}
const userIsOwner = !!(session?.user?.id && eventType.users.find((user) => (user.id = session.user.id)));
const userIsOwner = !!(session?.user?.id && eventType.owner?.id === session.user.id);
useTheme(isSuccessBookingPage ? props.profile.theme : "light");
const title = t(
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
@@ -354,10 +353,10 @@ export default function Success(props: SuccessProps) {
)}
{userIsOwner && !isEmbed && (
<div className="mt-2 ml-4 -mb-4">
<Link href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}>
<a 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")}
</a>
<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")}
</Link>
</div>
)}
@@ -380,7 +379,7 @@ export default function Success(props: SuccessProps) {
<div
className={classNames(
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 bg-white dark:border-neutral-700",
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 dark:border-darkgray-200 bg-white",
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
)}
role="dialog"
@@ -409,7 +408,7 @@ export default function Success(props: SuccessProps) {
</div>
<div className="mt-6 mb-8 text-center last:mb-0">
<h3
className="text-2xl font-semibold leading-6 text-neutral-900 dark:text-white"
className="text-2xl font-semibold leading-6 text-gray-900 dark:text-white"
data-testid={isCancelled ? "cancelled-headline" : ""}
id="modal-headline">
{needsConfirmation && !isCancelled
@@ -423,9 +422,10 @@ export default function Success(props: SuccessProps) {
: t("meeting_is_scheduled")}
</h3>
<div className="mt-3">
<p className="text-neutral-600 dark:text-gray-300">{getTitle()}</p>
<p className="text-gray-600 dark:text-gray-300">{getTitle()}</p>
</div>
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-300 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-200 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
{(isCancelled || reschedule) && cancellationReason && (
<>
<div className="font-medium">
@@ -500,7 +500,7 @@ export default function Success(props: SuccessProps) {
<>
<div className="mt-9 font-medium">{t("additional_notes")}</div>
<div className="col-span-2 mb-2 mt-9">
<p>{bookingInfo.description}</p>
<p className="break-words">{bookingInfo.description}</p>
</div>
</>
)}
@@ -574,7 +574,9 @@ export default function Success(props: SuccessProps) {
{!props.recurringBookings && (
<span className="text-bookinglight inline text-gray-700">
<span className="underline">
<Link href={`/reschedule/${bookingInfo?.uid}`}>{t("reschedule")}</Link>
<Link href={`/reschedule/${bookingInfo?.uid}`} legacyBehavior>
{t("reschedule")}
</Link>
</span>
<span className="mx-2">{t("or_lowercase")}</span>
</span>
@@ -593,7 +595,7 @@ export default function Success(props: SuccessProps) {
</>
) : (
<>
<hr className="border-bookinglightest dark:border-darkgray-300" />
<hr className="border-bookinglightest dark:border-darkgray-200" />
<CancelBooking
booking={{ uid: bookingInfo?.uid, title: bookingInfo?.title, id: bookingInfo?.id }}
profile={{ name: props.profile.name, slug: props.profile.slug }}
@@ -634,17 +636,16 @@ export default function Success(props: SuccessProps) {
? "&recur=" +
encodeURIComponent(new RRule(props.eventType.recurringEvent).toString())
: "")
}>
<a className="h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-neutral-700 dark:text-white">
<svg
className="-mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Google</title>
<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" />
</svg>
</a>
}
className="h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-neutral-700 dark:text-white">
<svg
className="-mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Google</title>
<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" />
</svg>
</Link>
<Link
href={
@@ -658,19 +659,17 @@ export default function Success(props: SuccessProps) {
"&subject=" +
eventName
) + (location ? "&location=" + location : "")
}>
<a
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Outlook</title>
<path d="M7.88 12.04q0 .45-.11.87-.1.41-.33.74-.22.33-.58.52-.37.2-.87.2t-.85-.2q-.35-.21-.57-.55-.22-.33-.33-.75-.1-.42-.1-.86t.1-.87q.1-.43.34-.76.22-.34.59-.54.36-.2.87-.2t.86.2q.35.21.57.55.22.34.31.77.1.43.1.88zM24 12v9.38q0 .46-.33.8-.33.32-.8.32H7.13q-.46 0-.8-.33-.32-.33-.32-.8V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h6.5V2.55q0-.44.3-.75.3-.3.75-.3h12.9q.44 0 .75.3.3.3.3.75V10.85l1.24.72h.01q.1.07.18.18.07.12.07.25zm-6-8.25v3h3v-3zm0 4.5v3h3v-3zm0 4.5v1.83l3.05-1.83zm-5.25-9v3h3.75v-3zm0 4.5v3h3.75v-3zm0 4.5v2.03l2.41 1.5 1.34-.8v-2.73zM9 3.75V6h2l.13.01.12.04v-2.3zM5.98 15.98q.9 0 1.6-.3.7-.32 1.19-.86.48-.55.73-1.28.25-.74.25-1.61 0-.83-.25-1.55-.24-.71-.71-1.24t-1.15-.83q-.68-.3-1.55-.3-.92 0-1.64.3-.71.3-1.2.85-.5.54-.75 1.3-.25.74-.25 1.63 0 .85.26 1.56.26.72.74 1.23.48.52 1.17.81.69.3 1.56.3zM7.5 21h12.39L12 16.08V17q0 .41-.3.7-.29.3-.7.3H7.5zm15-.13v-7.24l-5.9 3.54Z" />
</svg>
</a>
}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Outlook</title>
<path d="M7.88 12.04q0 .45-.11.87-.1.41-.33.74-.22.33-.58.52-.37.2-.87.2t-.85-.2q-.35-.21-.57-.55-.22-.33-.33-.75-.1-.42-.1-.86t.1-.87q.1-.43.34-.76.22-.34.59-.54.36-.2.87-.2t.86.2q.35.21.57.55.22.34.31.77.1.43.1.88zM24 12v9.38q0 .46-.33.8-.33.32-.8.32H7.13q-.46 0-.8-.33-.32-.33-.32-.8V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h6.5V2.55q0-.44.3-.75.3-.3.75-.3h12.9q.44 0 .75.3.3.3.3.75V10.85l1.24.72h.01q.1.07.18.18.07.12.07.25zm-6-8.25v3h3v-3zm0 4.5v3h3v-3zm0 4.5v1.83l3.05-1.83zm-5.25-9v3h3.75v-3zm0 4.5v3h3.75v-3zm0 4.5v2.03l2.41 1.5 1.34-.8v-2.73zM9 3.75V6h2l.13.01.12.04v-2.3zM5.98 15.98q.9 0 1.6-.3.7-.32 1.19-.86.48-.55.73-1.28.25-.74.25-1.61 0-.83-.25-1.55-.24-.71-.71-1.24t-1.15-.83q-.68-.3-1.55-.3-.92 0-1.64.3-.71.3-1.2.85-.5.54-.75 1.3-.25.74-.25 1.63 0 .85.26 1.56.26.72.74 1.23.48.52 1.17.81.69.3 1.56.3zM7.5 21h12.39L12 16.08V17q0 .41-.3.7-.29.3-.7.3H7.5zm15-.13v-7.24l-5.9 3.54Z" />
</svg>
</Link>
<Link
href={
@@ -684,34 +683,31 @@ export default function Success(props: SuccessProps) {
"&subject=" +
eventName
) + (location ? "&location=" + location : "")
}>
<a
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Office</title>
<path d="M21.53 4.306v15.363q0 .807-.472 1.433-.472.627-1.253.85l-6.888 1.974q-.136.037-.29.055-.156.019-.293.019-.396 0-.72-.105-.321-.106-.656-.292l-4.505-2.544q-.248-.137-.391-.366-.143-.23-.143-.515 0-.434.304-.738.304-.305.739-.305h5.831V4.964l-4.38 1.563q-.533.187-.856.658-.322.472-.322 1.03v8.078q0 .496-.248.912-.25.416-.683.651l-2.072 1.13q-.286.148-.571.148-.497 0-.844-.347-.348-.347-.348-.844V6.563q0-.62.33-1.19.328-.571.874-.881L11.07.285q.248-.136.534-.21.285-.075.57-.075.211 0 .38.031.166.031.364.093l6.888 1.899q.384.11.7.329.317.217.547.52.23.305.353.67.125.367.125.764zm-1.588 15.363V4.306q0-.273-.16-.478-.163-.204-.423-.28l-3.388-.93q-.397-.111-.794-.23-.397-.117-.794-.216v19.68l4.976-1.427q.26-.074.422-.28.161-.204.161-.477z" />
</svg>
</a>
}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Office</title>
<path d="M21.53 4.306v15.363q0 .807-.472 1.433-.472.627-1.253.85l-6.888 1.974q-.136.037-.29.055-.156.019-.293.019-.396 0-.72-.105-.321-.106-.656-.292l-4.505-2.544q-.248-.137-.391-.366-.143-.23-.143-.515 0-.434.304-.738.304-.305.739-.305h5.831V4.964l-4.38 1.563q-.533.187-.856.658-.322.472-.322 1.03v8.078q0 .496-.248.912-.25.416-.683.651l-2.072 1.13q-.286.148-.571.148-.497 0-.844-.347-.348-.347-.348-.844V6.563q0-.62.33-1.19.328-.571.874-.881L11.07.285q.248-.136.534-.21.285-.075.57-.075.211 0 .38.031.166.031.364.093l6.888 1.899q.384.11.7.329.317.217.547.52.23.305.353.67.125.367.125.764zm-1.588 15.363V4.306q0-.273-.16-.478-.163-.204-.423-.28l-3.388-.93q-.397-.111-.794-.23-.397-.117-.794-.216v19.68l4.976-1.427q.26-.074.422-.28.161-.204.161-.477z" />
</svg>
</Link>
<Link href={"data:text/calendar," + eventLink()}>
<a
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
download={props.eventType.title + ".ics"}>
<svg
version="1.1"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000"
className="mr-1 -mt-1.5 inline-block h-4 w-4">
<title>{t("other")}</title>
<path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" />
</svg>
</a>
<Link
href={"data:text/calendar," + eventLink()}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
download={props.eventType.title + ".ics"}>
<svg
version="1.1"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000"
className="mr-1 -mt-1.5 inline-block h-4 w-4">
<title>{t("other")}</title>
<path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" />
</svg>
</Link>
</div>
</div>
@@ -850,6 +846,17 @@ export function RecurringBookings({
}
const getEventTypesFromDB = async (id: number) => {
const userSelect = {
id: true,
name: true,
username: true,
hideBranding: true,
theme: true,
brandColor: true,
darkBrandColor: true,
email: true,
timeZone: true,
};
const eventType = await prisma.eventType.findUnique({
where: {
id,
@@ -868,17 +875,17 @@ const getEventTypesFromDB = async (id: number) => {
locations: true,
price: true,
currency: true,
owner: {
select: userSelect,
},
users: {
select: userSelect,
},
hosts: {
select: {
id: true,
name: true,
username: true,
hideBranding: true,
theme: true,
brandColor: true,
darkBrandColor: true,
email: true,
timeZone: true,
user: {
select: userSelect,
},
},
},
team: {
@@ -922,6 +929,7 @@ const schema = z.object({
uid: z.string(),
email: z.string().optional(),
eventTypeSlug: z.string().optional(),
cancel: stringToBoolean,
});
const handleSeatsEventTypeOnBooking = (
@@ -955,7 +963,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const ssr = await ssrInit(context);
const parsedQuery = schema.safeParse(context.query);
if (!parsedQuery.success) return { notFound: true };
const { uid, email, eventTypeSlug } = parsedQuery.data;
const { uid, email, eventTypeSlug, cancel } = parsedQuery.data;
const bookingInfo = await prisma.booking.findFirst({
where: {
@@ -1018,27 +1026,18 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
};
}
if (!eventTypeRaw.users.length && eventTypeRaw.userId) {
// TODO we should add `user User` relation on `EventType` so this extra query isn't needed
const user = await prisma.user.findUnique({
where: {
id: eventTypeRaw.userId,
},
select: baseUserSelect,
});
if (user) {
eventTypeRaw.users.push({
...user,
avatar: "",
allowDynamicBooking: true,
});
}
}
eventTypeRaw.users = !!eventTypeRaw.hosts?.length
? eventTypeRaw.hosts.map((host) => host.user)
: eventTypeRaw.users;
if (!eventTypeRaw.users.length) {
return {
notFound: true,
};
if (!eventTypeRaw.owner)
return {
notFound: true,
};
eventTypeRaw.users.push({
...eventTypeRaw.owner,
});
}
const eventType = {
@@ -1050,7 +1049,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
};
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl) {
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl && !cancel) {
return {
redirect: {
destination: eventType.successRedirectUrl,
+26 -11
View File
@@ -3,7 +3,6 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { PeriodType, SchedulingType } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -15,6 +14,7 @@ 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";
import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@@ -64,7 +64,6 @@ export type FormValues = {
phone?: string;
}[];
customInputs: CustomInputParsed[];
users: string[];
schedule: number;
periodType: PeriodType;
periodDays: number;
@@ -85,6 +84,8 @@ export type FormValues = {
};
successRedirectUrl: string;
bookingLimits?: BookingLimit;
hosts: { userId: number }[];
hostsFixed: { userId: number }[];
};
export type CustomInputParsed = typeof customInputSchema._output;
@@ -111,15 +112,15 @@ export type EventTypeSetupProps = RouterOutputs["viewer"]["eventTypes"]["get"];
const EventTypePage = (props: EventTypeSetupProps) => {
const { t } = useLocale();
const utils = trpc.useContext();
const router = useRouter();
const { tabName } = querySchema.parse(router.query);
const {
data: { tabName },
} = useTypedQuery(querySchema);
const { data: eventTypeApps } = trpc.viewer.apps.useQuery({
extendsFeature: "EventType",
});
const { eventType, locationOptions, team, teamMembers, currentUserMembership } = props;
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
@@ -185,6 +186,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
bookingLimits: eventType.bookingLimits || undefined,
length: eventType.length,
hidden: eventType.hidden,
periodDates: {
startDate: periodDates.startDate,
@@ -194,12 +196,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
schedulingType: eventType.schedulingType,
minimumBookingNotice: eventType.minimumBookingNotice,
minimumBookingNoticeInDurationType: convertToNewDurationType(
"minutes",
findDurationType(eventType.minimumBookingNotice),
eventType.minimumBookingNotice
),
metadata,
hosts: !!eventType.hosts?.length
? eventType.hosts.filter((host) => !host.isFixed)
: eventType.users
.filter(() => eventType.schedulingType === SchedulingType.ROUND_ROBIN)
.map((user) => ({ userId: user.id })),
hostsFixed: !!eventType.hosts?.length
? eventType.hosts.filter((host) => host.isFixed)
: eventType.users
.filter(() => eventType.schedulingType === SchedulingType.COLLECTIVE)
.map((user) => ({ userId: user.id })),
},
resolver: zodResolver(
z
@@ -237,7 +244,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
/>
),
availability: <AvailabilityTab isTeamEvent={!!team} />,
team: <EventTeamTab eventType={eventType} teamMembers={teamMembers} team={team} />,
team: <EventTeamTab teamMembers={teamMembers} team={team} />,
limits: <EventLimitsTab eventType={eventType} />,
advanced: <EventAdvancedTab eventType={eventType} team={team} />,
recurring: <EventRecurringTab eventType={eventType} />,
@@ -278,6 +285,8 @@ const EventTypePage = (props: EventTypeSetupProps) => {
locations,
metadata,
customInputs,
hosts: hostsInput,
hostsFixed,
// We don't need to send send these values to the backend
// eslint-disable-next-line @typescript-eslint/no-unused-vars
seatsPerTimeSlotEnabled,
@@ -286,6 +295,11 @@ const EventTypePage = (props: EventTypeSetupProps) => {
...input
} = values;
const hosts: (typeof hostsInput[number] & { isFixed?: boolean })[] = [];
if (hostsInput || hostsFixed) {
hosts.push(...hostsInput.concat(hostsFixed.map((host) => ({ isFixed: true, ...host }))));
}
if (bookingLimits) {
const isValid = validateBookingLimitOrder(bookingLimits);
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
@@ -303,6 +317,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
updateMutation.mutate({
...input,
hosts,
locations,
recurringEvent,
periodStartDate: periodDates.startDate,
+64 -74
View File
@@ -8,6 +8,7 @@ import {
CreateEventTypeButton,
EventTypeDescriptionLazy as EventTypeDescription,
} from "@calcom/features/eventtypes/components";
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 { RouterOutputs, trpc, TRPCClientError } from "@calcom/trpc/react";
@@ -26,9 +27,10 @@ import {
DropdownMenuTrigger,
EmptyScreen,
Icon,
Shell,
showToast,
Switch,
Avatar,
AvatarGroup,
Tooltip,
} from "@calcom/ui";
@@ -37,8 +39,6 @@ import { HttpError } from "@lib/core/http/error";
import { EmbedButton, EmbedDialog } from "@components/Embed";
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
import Avatar from "@components/ui/Avatar";
import AvatarGroup from "@components/ui/AvatarGroup";
import { ssrInit } from "@server/lib/ssr";
@@ -64,27 +64,26 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
const { t } = useLocale();
return (
<Link href={`/event-types/${type.id}?tabName=setup`}>
<a
className="flex-1 overflow-hidden pr-4 text-sm"
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
<div>
<span
className="font-semibold text-gray-700 ltr:mr-1 rtl:ml-1"
data-testid={"event-type-title-" + type.id}>
{type.title}
<Link
href={`/event-types/${type.id}?tabName=setup`}
className="flex-1 overflow-hidden pr-4 text-sm"
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
<div>
<span
className="font-semibold text-gray-700 ltr:mr-1 rtl:ml-1"
data-testid={"event-type-title-" + type.id}>
{type.title}
</span>
<small
className="hidden font-normal leading-4 text-gray-600 sm:inline"
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
{readOnly && (
<span className="rtl:ml-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2">
{t("readonly")}
</span>
<small
className="hidden font-normal leading-4 text-gray-600 sm:inline"
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
{readOnly && (
<span className="rtl:ml-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2">
{t("readonly") as string}
</span>
)}
</div>
<EventTypeDescription eventType={type} />
</a>
)}
</div>
<EventTypeDescription eventType={type} />
</Link>
);
};
@@ -280,9 +279,8 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
{type.users?.length > 1 && (
<AvatarGroup
border="border-2 border-white"
className="relative top-1 right-3"
size={8}
size="sm"
truncateAfter={4}
items={type.users.map((organizer) => ({
alt: organizer.name || "",
@@ -297,7 +295,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
{t("hidden")}
</Badge>
)}
<Tooltip content={t("show_eventtype_on_profile") as string}>
<Tooltip content={t("show_eventtype_on_profile")}>
<div className="self-center rounded-md p-2 hover:bg-gray-200">
<Switch
name="Hidden"
@@ -310,7 +308,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
</Tooltip>
<ButtonGroup combined>
<Tooltip content={t("preview") as string}>
<Tooltip content={t("preview")}>
<Button
color="secondary"
target="_blank"
@@ -320,7 +318,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
/>
</Tooltip>
<Tooltip content={t("copy_link") as string}>
<Tooltip content={t("copy_link")}>
<Button
color="secondary"
size="icon"
@@ -350,7 +348,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-edit-" + type.id}
StartIcon={Icon.FiEdit2}
onClick={() => router.push("/event-types/" + type.id)}>
{t("edit") as string}
{t("edit")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -359,7 +357,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string}
{t("duplicate")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -372,18 +370,19 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
{t("embed")}
</EmbedButton>
</DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" />
<DropdownMenuSeparator />
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) && (
<DropdownMenuItem>
<DropdownItem
color="destructive"
onClick={() => {
setDeleteDialogOpen(true);
setDeleteDialogTypeId(type.id);
}}
StartIcon={Icon.FiTrash}
className="w-full rounded-none">
{t("delete") as string}
{t("delete")}
</DropdownItem>
</DropdownMenuItem>
)}
@@ -402,15 +401,13 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem className="outline-none">
<Link href={calLink}>
<a target="_blank">
<Button
color="minimal"
StartIcon={Icon.FiExternalLink}
className="w-full rounded-none">
{t("preview") as string}
</Button>
</a>
<Link href={calLink} target="_blank">
<Button
color="minimal"
StartIcon={Icon.FiExternalLink}
className="w-full rounded-none">
{t("preview")}
</Button>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -424,7 +421,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success");
}}>
{t("copy_link") as string}
{t("copy_link")}
</Button>
</DropdownMenuItem>
{isNativeShare ? (
@@ -445,7 +442,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
.then(() => showToast(t("link_shared"), "success"))
.catch(() => showToast(t("failed"), "error"));
}}>
{t("share") as string}
{t("share")}
</Button>
</DropdownMenuItem>
) : null}
@@ -456,7 +453,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal"
className="w-full rounded-none"
StartIcon={Icon.FiEdit}>
{t("edit") as string}
{t("edit")}
</Button>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -467,10 +464,10 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string}
{t("duplicate")}
</Button>
</DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" />
<DropdownMenuSeparator />
<DropdownMenuItem className="outline-none">
<Button
onClick={() => {
@@ -480,7 +477,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="destructive"
StartIcon={Icon.FiTrash}
className="w-full rounded-none">
{t("delete") as string}
{t("delete")}
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
@@ -502,7 +499,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
e.preventDefault();
deleteEventTypeHandler(deleteDialogTypeId);
}}>
{t("delete_event_type_description") as string}
{t("delete_event_type_description")}
</ConfirmationDialogContent>
</Dialog>
</div>
@@ -515,38 +512,34 @@ const EventTypeListHeading = ({
teamId,
}: EventTypeListHeadingProps): JSX.Element => {
return (
<div className="mb-4 flex">
<div className="mb-4 flex items-center space-x-2">
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<a>
<Avatar
alt={profile?.name || ""}
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size={8}
className="mt-1 inline ltr:mr-2 rtl:ml-2"
/>
</a>
<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>
<div>
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<a className="font-bold">{profile?.name || ""}</a>
<Link
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
className="font-bold">
{profile?.name || ""}
</Link>
{membershipCount && teamId && (
<span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
<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`}>
<a>
<Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
{membershipCount}
</Badge>
</a>
<Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
{membershipCount}
</Badge>
</Link>
</span>
)}
{profile?.slug && (
<Link href={`${CAL_URL}/${profile.slug}`}>
<a className="block text-xs text-neutral-500">{`${CAL_URL?.replace("https://", "")}/${
profile.slug
}`}</a>
<Link href={`${CAL_URL}/${profile.slug}`} className="block text-xs text-gray-500">
{`${CAL_URL?.replace("https://", "")}/${profile.slug}`}
</Link>
)}
</div>
@@ -581,10 +574,7 @@ const EventTypesPage = () => {
return (
<div>
<Shell
heading={t("event_types_page_title") as string}
subtitle={t("event_types_page_subtitle") as string}
CTA={<CTA />}>
<Shell heading={t("event_types_page_title")} subtitle={t("event_types_page_subtitle")} CTA={<CTA />}>
<WithQuery
customLoader={<SkeletonLoader />}
success={({ data }) => (
@@ -178,6 +178,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
throw new Error("User from session not found");
}
if (user.completedOnboarding) {
return { redirect: { permanent: false, destination: "/event-types" } };
}
return {
props: {
...(await serverSideTranslations(context.locale ?? "", ["common"])),
+1 -1
View File
@@ -1,5 +1,5 @@
import Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { MobileNavigationMoreItems, Shell } from "@calcom/ui";
export default function MorePage() {
const { t } = useLocale();
@@ -2,7 +2,9 @@ import { GetServerSidePropsContext } from "next";
import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { Meta } from "@calcom/ui";
import { getLayout } from "@components/auth/layouts/AdminLayout";
import { ssrInit } from "@server/lib/ssr";
@@ -11,7 +13,7 @@ function AdminAppsView() {
return (
<>
<Meta title={t("apps")} description={t("admin_apps_description")} />
<AdminAppsList baseURL="/settings/admin/apps" className="w-0" />
<AdminAppsList baseURL="/settings/admin/apps" />
</>
);
}
@@ -0,0 +1,2 @@
export { getServerSideProps } from "./[category]";
export { default } from "./[category]";
@@ -3,7 +3,9 @@ import { signIn } from "next-auth/react";
import { useRef } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, getAdminLayout as getLayout, Meta, TextField } from "@calcom/ui";
import { Button, Meta, TextField } from "@calcom/ui";
import { getLayout } from "@components/auth/layouts/AdminLayout";
import { ssrInit } from "@server/lib/ssr";
+3 -1
View File
@@ -1,6 +1,8 @@
import { GetServerSidePropsContext } from "next";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { Meta } from "@calcom/ui";
import { getLayout } from "@components/auth/layouts/AdminLayout";
import { ssrInit } from "@server/lib/ssr";
+3 -1
View File
@@ -1,7 +1,9 @@
import { GetServerSidePropsContext } from "next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { Meta } from "@calcom/ui";
import { getLayout } from "@components/auth/layouts/AdminLayout";
import { ssrInit } from "@server/lib/ssr";
+2 -1
View File
@@ -3,11 +3,12 @@ import { useRouter } from "next/router";
import { useState } from "react";
import { HelpScout, useChat } from "react-live-chat-loader";
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, getSettingsLayout as getLayout, Icon, Meta } from "@calcom/ui";
import { Button, Icon, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
@@ -5,6 +5,7 @@ import { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
import LicenseRequired from "@calcom/ee/common/components/v2/LicenseRequired";
import ApiKeyDialogForm from "@calcom/features/ee/api-keys/components/ApiKeyDialogForm";
import ApiKeyListItem from "@calcom/features/ee/api-keys/components/ApiKeyListItem";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -13,7 +14,6 @@ import {
Dialog,
DialogContent,
EmptyScreen,
getSettingsLayout as getLayout,
Icon,
Meta,
AppSkeletonLoader as SkeletonLoader,
@@ -2,21 +2,21 @@ import { GetServerSidePropsContext } from "next";
import { useSession } from "next-auth/react";
import { Controller, useForm } from "react-hook-form";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Badge,
Button,
ColorPicker,
Form,
getSettingsLayout as getLayout,
Meta,
showToast,
SkeletonButton,
SkeletonContainer,
SkeletonText,
Switch,
UpgradeTeamsBadge,
} from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
@@ -49,6 +49,7 @@ const AppearanceView = () => {
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 formMethods = useForm({
defaultValues: {
@@ -73,7 +74,8 @@ const AppearanceView = () => {
},
});
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (isLoading || isLoadingHasTeamPlan)
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (!user) return null;
@@ -180,18 +182,18 @@ const AppearanceView = () => {
<p className="font-semibold ltr:mr-2 rtl:ml-2">
{t("disable_cal_branding", { appName: APP_NAME })}
</p>
<Badge variant="gray">{t("pro")}</Badge>
{!dataHasTeamPlan?.hasTeamPlan && <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={!session.data?.user.belongsToActiveTeam}
disabled={!dataHasTeamPlan?.hasTeamPlan}
onCheckedChange={(checked) =>
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
}
checked={!session.data?.user.belongsToActiveTeam ? false : value}
checked={!dataHasTeamPlan?.hasTeamPlan ? false : value}
/>
</div>
</div>
@@ -6,6 +6,7 @@ import { Fragment } from "react";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
@@ -13,7 +14,6 @@ import {
Badge,
Button,
EmptyScreen,
getSettingsLayout as getLayout,
Icon,
List,
ListItem,
@@ -175,9 +175,7 @@ const CalendarsView = () => {
</div>
</div>
<div className="w-full border-t border-gray-200">
<p className="px-2 pt-4 text-sm text-neutral-500">
{t("toggle_calendars_conflict")}
</p>
<p className="px-2 pt-4 text-sm text-gray-500">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-2 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
@@ -1,6 +1,7 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
@@ -12,8 +13,8 @@ import {
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownItem,
DropdownMenuTrigger,
getSettingsLayout as getLayout,
Icon,
List,
ListItem,
@@ -83,7 +84,7 @@ const ConferencingLayout = () => {
}
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3" className="mb-1 space-x-2 rtl:space-x-reverse">
<h3 className="truncate text-sm font-medium text-neutral-900">{app.title}</h3>
<h3 className="truncate text-sm font-medium text-gray-900">{app.title}</h3>
</ListItemTitle>
<ListItemText component="p">{app.description}</ListItemText>
</div>
@@ -94,16 +95,17 @@ const ConferencingLayout = () => {
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<Button
<DropdownItem
type="button"
color="destructive"
StartIcon={Icon.FiTrash}
disabled={app.isGlobal}
StartIcon={Icon.FiTrash}
onClick={() => {
setDeleteCredentialId(app.credentialIds[0]);
setDeleteAppModal(true);
}}>
{t("remove_app")}
</Button>
</DropdownItem>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
@@ -3,12 +3,12 @@ import { useRouter } from "next/router";
import { useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import {
Button,
Form,
getSettingsLayout as getLayout,
Label,
Meta,
Select,
@@ -5,6 +5,7 @@ import { signOut } from "next-auth/react";
import { BaseSyntheticEvent, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
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";
@@ -21,7 +22,6 @@ import {
DialogFooter,
DialogTrigger,
Form,
getSettingsLayout as getLayout,
Icon,
ImageUploader,
Label,
@@ -181,7 +181,9 @@ const ProfileView = () => {
};
if (isLoading || !user || isLoadingAvatar || !avatar)
return <SkeletonLoader title={t("profile")} description={t("profile_description")} />;
return (
<SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
);
const defaultValues = {
username: user.username || "",
@@ -227,11 +229,7 @@ const ProfileView = () => {
{/* Delete account Dialog */}
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
<DialogTrigger asChild>
<Button
data-testid="delete-account"
color="destructive"
className="mt-1 border-2"
StartIcon={Icon.FiTrash2}>
<Button data-testid="delete-account" color="destructive" className="mt-1" StartIcon={Icon.FiTrash2}>
{t("delete_account")}
</Button>
</DialogTrigger>
@@ -1,18 +1,10 @@
import { GetServerSidePropsContext } from "next";
import { useForm } from "react-hook-form";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Button,
Form,
getSettingsLayout as getLayout,
Label,
Meta,
showToast,
Skeleton,
Switch,
} from "@calcom/ui";
import { Button, Form, Label, Meta, showToast, Skeleton, Switch } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";

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