Compare commits

..
490 Commits
Author SHA1 Message Date
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
Peer Richelsen de446db434 v2.4.3
release.yml / release (push) Canceled after 0s
2023-01-04 21:00:14 +01:00
81546412bf add missing variables for variable translations (#6271)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-04 19:57:47 +00:00
Peer Richelsen 459530fba3 Revert "sidebar improvements" (#6273) 2023-01-04 15:46:42 +00:00
Marlo Kesser 42766b42c0 fix localization issues with default locations (#5569) 2023-01-04 15:31:50 +00:00
Hariom Balhara 38b3563350 Feature/Routing Forms: Add the concepts of reusable Router and Router Linked Fields. (#5566)
* WIP

* Fix all TS errors

* Move to using isRouter everywhere

* wip

* Add Router Picker and fix TS issues

* Fix tests and add option to connect during duplication

* Misc changes

* Fixes

* Fixes

* Fix TS errors

* Disable certain options in Router

* Fix connected Form fields not updating

* Some fixes

* reert shell.tsx

* Disable already used router instead of removing it

* Import prisma from correct place

* Simplify loop using Object.entries

* SimpliAdd playwright test and fix issues found
2023-01-04 13:30:46 +00:00
Jeroen Reumkens 37159914cf 🧹 Removed old root UI components. (#6249)
* Removed old root UI components.

* Fixed import path

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

* Removed irrelevant todo

* Fixed merge issues.

* Fixed shell export
2023-01-04 14:05:00 +01:00
Peer Richelsen 268bf68532 sidebar improvements (#6259)
* added bottom-0 to sidebar nav

* open sidebar on right for RTL
2023-01-04 11:15:30 +00:00
sean-brydon b02c7e01d5 Move Apps to ui/components (#6245)
* Move Apps to components

* Update packages/features/ee/sso/page/teams-sso-view.tsx
2023-01-04 11:00:01 +00:00
45d10a3bc4 a11y: right-to-left improvements (#6235)
* fixed a ton of rtl issues

* removed duplicant

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-01-04 07:38:45 +00:00
Jeff LoiselleandPeer Richelsen 9250b20c1d fixes available times overflow bug on safari (#6208)
* fixes available times overflow bug on safari

* WIP

* remove extranous grid classes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-03 22:05:40 +00:00
sean-brydon d9da311141 Move Skeleton to ui/components (#6247)
* Inital Move

* Skeleton story
2023-01-03 15:54:00 +00:00
Nafees Nazik 89a52dad21 fix: cutoff text and align text (#6248) 2023-01-03 14:12:36 +00:00
Hariom Balhara 2a1acc1294 Fix lang and dir attributes not being set (#6243) 2023-01-03 11:09:05 +01:00
8302990f84 fix: use hasMounted to render calcom versionm (#6239)
* fix: use hasMounted to render calcom versionm

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

* added Credits component but struggling with import

* only made it work with v2 folder. sorry jerone

* added link to releases

* added link to cal.com

* removing script calcomversion for now until we need it

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-01-02 19:44:51 +00:00
LoneRifleandPeer Richelsen 5a03b61d63 refactor(web): remove fonts.css, rely on shared-globals.css (#6220)
`styles/fonts.css` is identical to `shared-globals.css`, as found in
`packages/ui/styles`, and as referenced by `styles/globals.css`. Remove
`fonts.css` and its references, leaning instead on the fonts described
in `shared-globals.css`

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-02 19:11:06 +00:00
github-actions[bot]andCrowdin Bot 6b6f239936 New Crowdin translations by Github Action (#6242)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-02 19:50:31 +01:00
Jeff LoiselleandPeer Richelsen 9c2748e8e9 adds team prefix to url when creating team (#6211)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-02 18:34:32 +00:00
Peer Richelsen bb5cc33737 Update README.md 2023-01-02 18:05:53 +01:00
Udit TakkarandPeer Richelsen 5616a64827 fix: use border-0 (#6233)
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-02 15:10:00 +00:00
33530ad8c8 Make cal.com version available on all pages, even on login page. (#6221)
* Make cal.com version available on all pages, even on login page.

* Extract out calcom version as a utility

* added version to login screen

* added ee and ce to version

* fixed lint

* Fix import

* removed  version info component

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-02 20:24:35 +05:30
Peer Richelsen fe14839f1b fixed sidebar position, added toHide to <LicenseRequired> (#6228)
* fixed sidebar position

* fixed sidebar position

* fixed sidebar position

* removed gradient overlay from user dropdown

* added toHide prop to license check to hide a component if a license is available

* show children if toHide is true
2023-01-02 10:13:27 +00:00
Hariom Balharaandkodiakhq[bot] 15123c1aa5 Add disable success page config[Platform Only] (#6225)
* Add disable success page config

* Change text

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-02 08:41:39 +00:00
Peer Richelsen 71ee9c72fe introduced size="fab" prop to Button, added to CTA buttons (#6176)
* introduced size="fab" prop to Button, added to CTA buttons

* increased size of FAB

* nit
2023-01-02 09:28:12 +01:00
github-actions[bot]andCrowdin Bot 5503539856 New Crowdin translations by Github Action (#6222)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-01 13:32:19 +01:00
sean-brydon 6548f01efa Show admin password top banner (#6226)
* Invalid-admin fake role

* Reset password link + shell
2023-01-01 11:19:58 +00:00
Udit TakkarandPeer Richelsen 518a4484d6 fix: add allowScroll prop (#6215)
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>
2022-12-30 18:16:37 +00:00
Siddhant KhareandPeer Richelsen 3535a25a59 fix: consistency on menu hover border bug 🔧 (#6218)
Signed-off-by: Siddhant Khare <Siddhantkhare2694@gmail.com>

Signed-off-by: Siddhant Khare <Siddhantkhare2694@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-30 18:04:54 +00:00
Stefano Maffeis 06cc6741e6 Make radio field id unique by adding custom input id to their identifier (#6224) 2022-12-30 18:52:03 +01:00
Peer Richelsen ed1478e1ba Update README.md 2022-12-30 10:17:31 +01:00
Jeff Loiselle 854c05d673 fixes required custom radio element on firefox (#6209)
Fixes https://github.com/calcom/cal.com/issues/6160
2022-12-28 19:43:18 +00:00
bcf5fb18c5 Cal Video (Daily) Recording (#6039)
* feat: wip

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

* feat: add download recording button

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

* moved video recording into /ee/, wrapped in LicenseRequired

* fix: security issues in downloading recording
updates designs

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

* feat: add upgradation banner

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

* chore: remove default room

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

* chore: fix type error

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

* fix: add type in get recording

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

* fix: add suggestions and zod type for recording

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

* fix: add types in getRecordings

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

* fix: type error

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

* fix: type error

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

* fix: finally all type errors fixed

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

* Server side validation for users in team plans for recordings

* fix: remove any type

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

* fix: type error

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Alan <alannnc@gmail.com>
2022-12-27 21:03:39 +00:00
3f822e814b event type setting refactor (#5959)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-26 10:55:58 +00:00
Peer Richelsen 4fd25c7215 fixed booking layout (#6188) 2022-12-26 16:11:28 +05:30
github-actions[bot]andCrowdin Bot f42bed2a55 New Crowdin translations by Github Action (#6175)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-26 11:25:47 +01:00
469c205033 fix: setting sidebar improvements (#6177)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2022-12-26 11:23:03 +01:00
Nafees Nazik 31551f3b9e fix: button focus (#6178) 2022-12-26 09:44:43 +00:00
Carina WollendorferandCarinaWolli 69925af17c Fix skeleton loader for /workflows (#6172)
* return latest bookings for example bookings for zapier

* remove mt on skeleton loader

* Revert "return latest bookings for example bookings for zapier"

This reverts commit b69f393a73a59a05fe99f01d8caa4ac0fd5cf992.

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-23 12:27:40 +00:00
Carina WollendorferandCarinaWolli 2502868ebc Return latest bookings for zapier example bookings (#6170)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-23 12:00:03 +01:00
zomars 38d242add9 v2.4.2
release.yml / release (push) Canceled after 0s
2022-12-22 17:12:32 -07:00
zomars c272e281cf Submodule sync 2022-12-22 17:12:13 -07:00
zomars d4e19b0fa4 Revert "Matches postgres image across projects"
This reverts commit c12877a58f.
2022-12-22 17:11:31 -07:00
zomars c12877a58f Matches postgres image across projects 2022-12-22 16:52:44 -07:00
af83350d04 replaced webhook icon with actual webhook logo (#6161)
* replaced webhook icon with actual webhook logo

* Apply suggestions from code review

* Linting

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-22 12:57:44 -07:00
sean-brydonandPeer Richelsen 9d0cf6be0b No more modules folder 🔥 (#6158)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-22 19:06:26 +00:00
zomars 2fe9c782d4 Lowercase usename checks again. 2022-12-22 11:09:31 -07:00
zomars d0a3d8d144 Link fixes
commit 56c1608deaf1921a80dc71bcbd107e772dd656f4
Author: Omar López <zomars@me.com>
Date:   Wed Dec 21 19:26:04 2022 -0700

    Update link.ts

commit eab43d833515cabc7acf2f973c2d2837ac1a0332
Author: zomars <zomars@me.com>
Date:   Wed Dec 21 19:03:02 2022 -0700

    Prevents 500 on TRPC error

commit 3a6970d5e5fab54136b379e1b2ccd7acf515a809
Author: zomars <zomars@me.com>
Date:   Wed Dec 21 18:15:26 2022 -0700

    Encoding fixes
2022-12-22 10:52:50 -07:00
sean-brydonandzomars cefcb1eaf8 Feature/booking filters (#6099)
* EventTypeFilter UI working

* Tidy + fix avatar + fix overflow

* Component store

* WIP - switchin to query filtering instead of state

* Push working

* Checkbox approach - weird conditional toggle for all bookings

* WIP

* the master query hook :O

* useTypedQuery

* Working typed query

* Typed query hook

* Add optional keys

* safeParse query params

* Add use callback

* Fix re-renders

* Remove local version of local query and use util branch

* Working team filters!

* working people filter!

* Tidy up

* NIT

Co-authored-by: zomars <zomars@me.com>
2022-12-22 12:35:01 +00:00
sean-brydonandPeer Richelsen 83b60de640 Chor/move table actions (#6157)
* Move table actions

* Move table actions

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-22 12:41:43 +01:00
Hariom BalharaandAlex van Andel a076418989 Hotfix/Event Type Save Error with Multiple Durations (#6154)
* Fix .length being undefined when form is submitted without any change to duration

* Fix type error

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-22 11:13:33 +00:00
zomars 64ecce1ccb Submodule sync 2022-12-21 17:49:10 -07:00
zomars cd56a5d4f0 Removes deprecated rejectOnNotFound 2022-12-21 17:48:15 -07:00
zomars d5ca121ac6 Revert "team banner (#5956)"
This reverts commit 463787b607.
2022-12-21 17:39:07 -07:00
zomars 1e20e80b65 v2.4.1
release.yml / release (push) Canceled after 0s
2022-12-21 17:18:38 -07:00
github-actions[bot]andCrowdin Bot 75ac3fd40c New Crowdin translations by Github Action (#6150)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-21 17:17:43 -07:00
Alex van Andelandkodiakhq[bot] 3b91cc7f02 75px -> 88px (#6152)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-21 17:17:24 -07:00
23450b61e0 Refactor/email confirm links (#6151)
* Booking confirmation link improvements

* Cleanup

* Update webhooks payload

* Removing unneeded dependency

* Feedback

* Matches enum style to rest of codebase

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>
2022-12-21 17:15:51 -07:00
zomars dcc226976c Update labeler.yml 2022-12-21 17:03:00 -07:00
zomars 196fde72f6 Update labeler.yml 2022-12-21 16:59:19 -07:00
github-actions[bot]andCrowdin Bot 1e913cd406 New Crowdin translations by Github Action (#6149)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-21 22:16:32 +01:00
02425702d1 Fixes/stripe improvements (#5698)
* Enables require confirmation after payment its made

* Various Stripe improvements

* Use constant

* Merge branch 'main' into fixes/stripe-improvements

* Update getStripeAppKeys.ts

* submodule sync

* Submodule sync

* Revert

* Update apps/web/components/booking/BookingListItem.tsx

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

Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-21 20:31:09 +00:00
github-actions[bot]andCrowdin Bot 2494655369 New Crowdin translations by Github Action (#6148)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-21 21:18:49 +01:00
0e0a78c47e Fix/Round Robin (#6121)
* Identify owner for a timeslot and check busyness against his schedule only

* Fix TS errors

* Fix flatmap

* Fix xisting unit tests

* Prevent duplicate slots by merging, userId -> userIds (multi)

* Small fix to potential undefined

* Moved duplicate prevention to buildSlots function

* Apply date override on a per user basis

* Prevent -1 being added to computedLocalAvailability

* Removed console.log

* Apply override properly on COLLECTIVE

* Default timeZone to UTC when undefined

* isSame doesn't work when the day shifts, isBetween instead

* Iterate over all slot.userIds schedules to identify the first user schedule that matches

* add round-robin test

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-21 19:32:42 +00:00
Leo GiovanettiandAlex van Andel ed02c10f54 Fixing event type duration query param (#6146)
* Fixing event type duration query param

* Ignoring query param instead of erroring out

* Removing unneeded string

* Amended useEffect for simplication

* Refactored useRouterQuery into new useTypedQuery

* Revert "Refactored useRouterQuery into new useTypedQuery"

This reverts commit b3b0aa6077a75ae37c0220cce5044b7655ff602f.

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-21 17:23:02 +00:00
github-actions[bot]andCrowdin Bot 9c40f76184 New Crowdin translations by Github Action (#6140)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-21 09:45:47 -07:00
Alex van AndelandJoe Au-Yeung 8eda30d88c Add 23:59 as a valid availability select option (#6137)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-12-21 16:13:50 +00:00
alannncandAlex van Andel 9de19d35a8 Change startTime to current date - 2 days if startTime it's on current month (#6117)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-21 15:11:42 +00:00
0770403ca5 Improved borders on teams profile pages. (#6141)
* Improved borders on teams profile pages.

* Improved border styles on dynamic booking pages (pro+free) and improved darkmode avatar border.

* Fixed badge hover state in dark mode

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-21 15:06:00 +01:00
Mohammed CherfaouiandAlex van Andel 1a45cb35c1 remove cancel button for unconfirmed past bookings (#6142)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-21 13:43:31 +00:00
Jeroen ReumkensandPeer Richelsen 15cdff76d9 Feat/add screenshot og image variant (#6138)
* Added screenshot variant of og image (used in website).

* Added option to rotate background image in og image.

* Converted wrapper props to interface instead of inline type declaration.

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-21 10:39:14 +00:00
Hariom Balhara 8c4eb4b5fe Fix/Embed - Fix for Duration query param getting removed (#6139) 2022-12-21 15:44:56 +05:30
zomars ae30aafafe Fixes scroll on medium screens 2022-12-20 18:27:16 -07:00
3b74717591 Fix: user impersonation switch showing the wrong value (#6126)
* fix: remove register method because ref is not forwarded

* fix: prefetch user to get the correct value for the switch component

* feat: add optimistic update

* feat: disable update button if not dirty

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-12-20 23:13:31 +00:00
Leo Giovanetti 7a6349b4c0 [Hotfix] Fixing direct link encoding (#6131) 2022-12-20 16:00:33 -07:00
3b51fff88d New Crowdin translations by Github Action (#6134)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-20 23:48:06 +01:00
+5 2a0b0be9d0 [CAL-211] Confirmed booking page for rescheduled event should show previous time and reschedule reason (#5369)
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: José Romary Brandão Jr <joseromarybrandao@gmail.com>
Co-authored-by: csangam <coolmagnas@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: José Romary Brandão Jr <joseromarybrandao@gmail.com>
Co-authored-by: csangam <coolmagnas@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-20 22:46:28 +00:00
f373c9d818 Webhooks for team event-types (#6120)
* add webhooks tab for team event types

* show nr of active event types

* type fixes + show team webhooks only in event type single view

* adding and deleting webhook

* fix disabling and enabling team webhooks

* add editing webhook

* code clean up

* add installed apps

* code clean up

* fix reserved subscriber url when creating event

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-20 15:34:37 -07:00
540bf3a1cb Fix app categories (#6016)
* Fix app metas

* Optimize categories on app store

* Remove commented code

* Remove console log

* Update apps/web/pages/apps/index.tsx

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

* Add categories to missing app metadata

* Fix type error

* Type fix

* Type fixes

* More type fixes

* Clean up

* Fix build error

* No leaky please

* Remove comment

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-20 15:15:06 -07:00
2d9064e92f fix: show email in outlook (#5897)
* fix: change external id to owner address

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

* feat: add email prop in calendar

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

* fix: only display for outlook

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

* fix: add email for apple calendar

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

* fix: type error and use description

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

* fix: try  using email from /me endpoint

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

* fix: fallback to principal name when mail is not there

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

* chore: use empty string for fallback

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-12-20 21:50:20 +00:00
alannncandPeer Richelsen 9487819ec1 Add try catch for event type duplicate, fixed catch error loggin (#6130)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-20 21:38:15 +00:00
zomars 49bf14b196 Submodule sync 2022-12-20 13:42:01 -07:00
344f16d6cc Fixing multiple duration default issues (#6114)
* Fixing multiple duration default issues

* Ensure that when calculation time slots we use 1 as a minimum interval, since 0 min meetings would result in an infinite loop.

Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-20 17:49:24 +00:00
github-actions[bot]andCrowdin Bot ccbf3b1e54 New Crowdin translations by Github Action (#6125)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-20 16:22:25 +01:00
Nafees Nazik dcf0fad35d fix: remove duplicate meta component (#6124) 2022-12-20 15:13:30 +00:00
github-actions[bot] a836800c69 New Crowdin translations by Github Action (#6123) 2022-12-20 13:49:10 +00:00
github-actions[bot]andCrowdin Bot b8c6ac43f7 New Crowdin translations by Github Action (#6122)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-20 14:42:23 +01:00
f5e0c6b384 feat: disable update button unless any input value changed in general settings (#5686)
* feat: disable update button unless any input value changed

* fix: reset dirtyFields on success

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-12-20 13:25:15 +00:00
sean-brydonandAlex van Andel 2a5dd9035f Fixing booking limits (#6119)
* Fix logic

* Some performance improvements

* Return early to reduce depth

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-20 13:10:54 +00:00
2cddad4336 Typed query hook (#6034)
* Typed query hook

* Add optional keys

* safeParse query params

* Add use callback

* Fix re-renders

* Add schemas to support query params - fix keys

* Update packages/lib/hooks/useTypedQuery.ts

* Fix submodules

* Fix website submodule

* Reset website submodule

* Merry Xmas Sean

* Whoops

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-19 22:34:46 +00:00
Peer Richelsen 4bcbff51b1 /event-types improvements (#6113) 2022-12-19 19:35:01 +00:00
Peer Richelsen ae541e4c37 /bookings: only show timezone popover on hover (#6112) 2022-12-19 13:56:34 -05:00
Omar López f254711d85 Makes WizardForm translation agnostic (#6111) 2022-12-19 11:30:54 -07:00
Nafees Nazik 533e208643 Fix: Position of the "Add" button (#6110)
Fixes https://github.com/calcom/cal.com/issues/6104
2022-12-19 12:53:46 -05:00
Jeroen ReumkensandPeer Richelsen 795574ef88 Ensure topbanner is always on top of page layout rather than inside main content (#6098)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-19 17:37:20 +00:00
Nafees NazikandPeer Richelsen 48611d5c99 Fix: pending payment label appears twice (#6109)
* fix: remove duplicate label

* feat: replace Tag component with the badge component

* refactor: remove unused Tag component

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-19 16:59:11 +00:00
Leo GiovanettiandPeer Richelsen 11a07961d0 Fixing permission for admin wizard (#6061)
* Fixing permission

* Update apps/web/pages/auth/setup/index.tsx

* Correcting error page code

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-19 14:50:25 +00:00
6be0fd52e7 fix: add a returnTo query param in new team url path (#5967)
* fix: remove optional from schema.

* schema success state is always true  because of the optional property.
 so we are getting undefined if returnTo query Param doesn't exist

* fix: add a return to query param

* nit: turn querySchema into an object

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-19 15:38:33 +01:00
Joe Au-Yeung 78da7ba338 Set seatsShowAttendees to false (#6025) 2022-12-19 14:12:18 +00:00
9a72c648c5 Removes EMAIL_ADDRESS action (#6102)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-19 13:50:14 +00:00
Leo Giovanetti 3e42da7fc7 Fixing wizard validation (#6068) 2022-12-19 13:34:40 +00:00
github-actions[bot]andCrowdin Bot 1d986cad04 New Crowdin translations by Github Action (#6101)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-19 12:41:52 +01:00
Nafees Nazik f603e4eff2 fix: add contact_our_support_team i18n key (#6097) 2022-12-19 12:37:41 +01:00
sean-brydonandPeer Richelsen 1f4e6c1987 Migrates V2 Select to new ui/components (#5400)
* v2 select to components folder + docs

* Fix type errors

* fix type checks

* Fix imports

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-18 23:16:20 +00:00
Hariom Balhara e57f734e79 Test/Embed/Reschedule (#6056)
* Add reschedule embed test

* Add comments
2022-12-18 19:24:44 +00:00
github-actions[bot]andCrowdin Bot 7fad76ae23 New Crowdin translations by Github Action (#6095)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-18 13:52:44 +01:00
Maxim GandAlex van Andel 7659a016b4 Use the same locale for event description. (#5674)
* Use the same locale for event description.

Currently all headers  of the event description use organizer's language except description part. This is not suitable to see different languages withing the same text block.

Change description header to use the same locale as others.

* Translate app_status in organizer language too

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-18 12:36:30 +00:00
Leo GiovanettiandPeer Richelsen 34fe1c4a95 Fixing navigation and visual issues (#6067)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-18 10:56:21 +00:00
Mehran ShahbazandPeer Richelsen 205f73c4bc React feather refactor (#6090)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-18 10:44:44 +00:00
Peer Richelsen c4bd2167e1 pluralise german strings (#6092) 2022-12-18 10:50:51 +01:00
github-actions[bot]andCrowdin Bot 49a7111b66 New Crowdin translations by Github Action (#6089)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-18 10:43:31 +01:00
Carina WollendorferandCarinaWolli ca045a040b add meeting url to dynamic text variables of custom workflow templates (#5958)
* small design fix for time unit select

* save video cal url to booking in metaData

* save video url when bookings are confirmed

* fix e2e tests

* remove not needed waitForNavigation

* add metadata to booking created webhook payload

* add migration

* fix type error

* add meeting url variable to dropdown

* add meeting url to custom template

* add BookingMetaDataSchema

* add meeting url to email reminder

* add meeting url to sms notifcation

* adjust e2e tests

* add bookingMetadataSchema

* add missing change from merge

* fix spelling

* fix type error

* code clean up

* fix e2e snapshot

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-18 02:04:06 +00:00
fiskareandAlex van Andel a8b4198bf7 Update common.json (#6080)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-17 23:10:40 +00:00
Alex van Andel 2ab24e530b Nope on test (#6085)
* Nope on test

* Use // instead of /**/

* Investigating why types are failing
2022-12-17 22:51:00 +00:00
734b06dfdf fix: prevent overflow in booking description (#6077)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-17 22:49:15 +01:00
github-actions[bot] 467084993b New Crowdin translations by Github Action (#6082) 2022-12-17 18:42:35 +00:00
Leo Giovanetti 1e2c8516d2 Workflow direct email disabling (#6075)
* Disabling direct emails from workflow (#6074)

* Commenting out the option fro logic too
2022-12-17 11:11:59 -07:00
Leo Giovanetti d57738f325 Disabling direct emails from workflow (#6074) 2022-12-17 12:26:54 +01:00
Carina WollendorferandCarinaWolli c39a7dba85 don't send scheduled (#6072)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-16 22:58:43 +00:00
Carina WollendorferandCarinaWolli fd37397e5c Fix/workflow email (#6069)
* teams plan needed

* disable

* disable

* type fix

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-16 21:11:08 +00:00
df0a522656 fix: use callback url in signup (#6010)
* fix: use callback url in signup

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

* fix: do login after sign up and redirect to onboarding

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

* chore: add telemetry

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

* fix: error handling

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-16 20:40:20 +00:00
916468b8cc feat: adding custom field of type PHONE (#5623)
* feat: adding custom field of type PHONE

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

* fix: null phone number bug and removed console log

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

* fix: decrease iteration

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-16 12:39:41 -07:00
Peer Richelsen b789e58624 added emptyscreen for app store search (#6065)
* added emptyscreen for app store search

* nit
2022-12-16 16:02:29 -03:00
Leo Giovanetti 4368536141 Fixing Admin Wizard step 1 blocking step 2 (#6063) 2022-12-16 18:41:07 +01:00
Alex van Andel 9eef9766ce Truncate month from November to Nov on Booking page (#6058) 2022-12-16 13:35:56 +00:00
Peer Richelsen 68dbfbd7d0 amie.so app (#6053)
* added amie

* changed category

* fixed category
2022-12-16 13:11:32 +00:00
Alex van Andel 692a84a86e Fixes the start and end time mobile view (#6057) 2022-12-16 12:46:15 +00:00
Alex van AndelandPeer Richelsen 1671095c94 Fixes to dialog for date override & dialog overflow fix (#6055)
* Fixes to dialog for date override & dialog overflow fix

* Fixed datepicker border on mobile

* removed max-height from dialog, needs testing

Co-authored-by: Peer Richelsen <peer@cal.com>
2022-12-16 13:27:42 +01:00
zomars 3b896bee5c v2.4.0
release.yml / release (push) Canceled after 0s
2022-12-15 15:59:08 -07:00
zomars cc1985442e Submodule sync 2022-12-15 15:58:13 -07:00
Omar López 630f438b32 Upgrades prisma / Syncs packages (#6044)
* Syncs packages

* Upgrades zod
2022-12-15 15:56:48 -07:00
6fae01d45a added auto animate to installed apps, fixed container size (#6046)
* added auto animate to installed apps, fixed container size, horizontal scroll

* Update packages/ui/v2/modules/booker/DatePicker.tsx

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-15 22:47:31 +00:00
dependabot[bot] 7009373b11 Bump loader-utils from 1.4.0 to 1.4.2 (#6045)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  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>
2022-12-15 15:18:40 -07:00
c2f150dbab Feat/verify phone number (#6035)
* first implementation of verifying phone number

* add UI + logic for verifying phone number flow

* check if all phone numbers are verified before submit

* add numberVerification pending

* only send SMS to verified numbers

* fix design for mobile view

* check if phone number is verified before testing action

* add back message service id check

* add TWILIO_VERIFY_SID to .env.example

* code clean up

* add TWILIO_VERIFY_SID to README.md instructions

* save new verified numbers

* fix wrongly thrown error for new verified numbers

* use false as default value for isVerificationPending paramater

* add translations

* add migration file

* code clean up

* don't throw error if phone number is not needed

* Feedback

* Shows error when Twillio isn't properly setup

* Type fixes

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-15 21:54:40 +00:00
688541923b Save meeting url to metadata of bookings and add it to webhook payload (#5773)
* small design fix for time unit select

* save video cal url to booking in metaData

* save video url when bookings are confirmed

* fix e2e tests

* remove not needed waitForNavigation

* add metadata to booking created webhook payload

* add migration

* fix type error

* adjust e2e tests

* add bookingMetadataSchema

* add missing change from merge

* add new line to expected test result

* update snapshot

* fix e2e tests

* Update confirm.ts

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2022-12-15 21:43:07 +00:00
20402ac26c Fixes for testing workflow action (#6005)
* additional checks for testing workflow action

* fix old variable in testAction mutaiton

* remove workflowId

* only allow team plan to test action

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-15 20:48:22 +00:00
Omar López a54952b41a Adds tests for date overrides (#6027) 2022-12-15 13:19:35 -07:00
github-actions[bot]andCrowdin Bot 987756c4d4 New Crowdin translations by Github Action (#6022)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-15 21:18:33 +01:00
Leo Giovanetti 3ee294ffc5 Fixing admin wizard translations (#6038) 2022-12-15 15:03:57 +00:00
Leo Giovanetti a638708871 Baikal CalDAV error hint (#6021) 2022-12-15 13:45:54 +00:00
Peer RichelsenandHariom Balhara 3456cc5521 vimcal app (#5791)
* wip vimcal app

* added vimcal blog link

* todo: filter out external calendar apps

* Make it a non calendar type app with category and variant as calendar

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2022-12-15 08:44:21 +00:00
GitStart 546f0c3881 A Playwright test for "Copy Typeform Redirect URL link" action (#5716)
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>
Co-authored-by: José Romary Brandão Jr <joseromarybrandao@gmail.com>
Co-authored-by: csangam <coolmagnas@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Rubens Rafael <70234898+RubensRafael@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Fixes https://github.com/calcom/cal.com/issues/4570
2022-12-15 11:15:09 +05:30
7b18272d60 SMS workflow actions only for Teams (#6003)
* disable sms actions for users without team

* add isTeamsPlan query

* disable SMS action if user is not in a team

* remove console log

* disable SMS actions also in AddActionDialog

* throw error is users wants to add sms or edit  to sms action

* code clean up

* Get actions options from backend. cleanup

* Typefix

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-15 00:11:57 +00:00
Peer Richelsen cf6efabdbb added layout for empty month to go to the next month (#6011) 2022-12-14 22:43:21 +00:00
Udit TakkarandPeer Richelsen 92d15a00f5 fix: use cal url (#6013)
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>
2022-12-14 17:43:00 +00:00
2f2b72dd54 Feature/date overrides (#5991)
* Initial incomplete (but mostly functional) push of date override functions

* Fixed date shifting on load

* Bring back minDate (automatically disable all dates before current date)

* Fix type error

* Supply working hours to render available dates

* Converted to SSR

* moving defaultValues to the backend

* Improv. as filter can be achieved within the reduce

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

* Double inversion -> single, as it is an early return

* uniq() exit - not needed anymore

* Typefixes

* It's overriding dates :D

* Fixed duplication DateOverrides in list

* Implemented changing the month

* Make dateOverrides an optional param

* Fixed test (which requires dateOverrides due to auto-typing)

* Prevent a full update on set as default from list view

* Added some extra keys to keep ts happy

* Only allow a single date override per date

* Disallow editing excludedDates to the same date

* Bring back duplicate key ?.?

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-14 17:30:55 +00:00
8fd5d6b5b5 Calendar Weekly Scheduler (#5653)
* storybook v2 init

* Merge config into storybook vite build

* Remove path

* Storybook config tweaks

* Added styles and settings for storybook v2, and started working on button documentation and examples.

* Badges + flex wrap on mobile

* Breadcrumbs+button+avatar

* Checkbox

* Input + moving files around

* WIP table

* WIP table grid

* Replaced imports for new components.

* Added first steps for varianttable.

* Small alignment fix.

* Custom Args Table - With scrollbar

* Adding table to components that need it + darkmode

* Add intro

* Fix types

* Remove V1 storybook and replace with V2

* Fix badge type error

* Fixed storybook dependencies

* Added cover image to storybook

* Remove vita from ts config, we dont use vite.

* Fixed button import.

* Explained postcss pseudo plugin.

* Fixed badge import.

* Add Avatar Stories

* ButtonGroup Stories

* Fixed imports

* Add checkbox stories

* Inital state plannning

* Inital state combined with passed in props

* Start of UI work

* Able to change dates?

* Add dynamic hour props

* Get grid system setup correctly

* Show events on grid

* Weird sizing issue but events placed correctly gridstart

* CAL styled calendar event component

* availability WIP ish

* Blocking days! + Block days < today

* Kinda working time line

* Rename grid stop + formatting

* Handle sorting events if required.

* Add util for getting startDate bassed on weekday

* Remove event stories for now

* Implement gridstops per hour to be dyamic

* New CSS Grid + offsetbased positoning

* Fix weird Z-Index issues on hover

* Implement blocklist again with new format

* Side by side events working - styling needs work

* New design of overlap

* Overlapping? Working :O

* Cleanup

* WIP hover state

* Werid border issue

* fix translate issue

* Kinda working with overflow

* Fix overflow

* Progressive date blocking

* Cleanup

* Fix double render of blocked list

* WIP mobile implementaiton

* Trying to fix CSS

* Extract CSS to styles.css to allow media queries

* Improve documentation - allow args to be changed in storybook

* Fix hover showing even if disabled

* WIP cols auto approach

* Merge blocking dates

* Fix zindex

* Fix hover position

* Fix Z-Index issues on hover and blocking events

* Re add onclick handler

* Fix overlapping blocking dates

* Fix scaling for datevalues columns

* Date values closer to DS

* Blocked List Tidy up

* Storybook + file tidy up

* Little tidy up

* Fix offsets

* Remove event hover

* Fix random bg-red-500

* Fix import

* FIx blocking cells appearing above start Date

* Fix truncation

* Fix border overlap

* Overlap a little nicer

* Condtional 80% sizing

* Nitpicks

* Fix today height and top breaking

* Add text left to time stamp

* Support string dates

* Add shalow to reduce re-renders

* Rename to Calendar instead of scheduler

* Fix 3 overlapping events

* Fix merge type error

* Fix destructuring

* NITS

* Move to features package

Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-14 13:36:10 +00:00
Joe Au-Yeung 2078f12a85 Outlook - list all calendars (#6004) 2022-12-14 11:04:32 +00:00
github-actions[bot]andCrowdin Bot 77b8eb8a7f New Crowdin translations by Github Action (#6018)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-14 11:57:02 +01:00
Peer Richelsenandsean-brydon 463787b607 team banner (#5956)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-12-14 09:04:45 +01:00
alannncandPeer Richelsen 1f4e7ca695 Adding new middleware and using it for test action workflow (#6017)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-13 21:34:21 +00:00
Jeroen ReumkensandPeer Richelsen 9eb52bbfb2 #5613 Optimise social images by utilising next image api. (#5997)
* #5613 Optimise social images by utilising next image api.

* removed unused import

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-13 21:22:38 +00:00
Leo GiovanettiandPeer Richelsen 61e6f76bc6 Direct links to accept/reject a booking (#5939)
* Impl

* Added reason

* Secondary reject CTA

* Update packages/emails/src/components/Info.tsx

* Adjusting recurring info

* Not showing reason for rejecting if none given

* Feedback

* Error handling + style fixes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-13 22:09:28 +01:00
github-actions[bot]andCrowdin Bot 2e6c3a6068 New Crowdin translations by Github Action (#6001)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-13 19:11:45 +01:00
8f48ecafec Misc fixes (#5988)
* Address reports

* Clean up

* remove activeOn in where

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-13 17:44:53 +00:00
Peer Richelsen 8849d1148c removed z-index from topbanner (#6008) 2022-12-13 23:04:25 +05:30
98a4684a2b Impl (#5993)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-13 10:25:30 -03:00
Omar López da0bdfacae Handle membership updates in Stripe (#5992) 2022-12-13 12:43:37 +00:00
github-actions[bot]andCrowdin Bot 68c7425922 New Crowdin translations by Github Action (#5996)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-13 11:07:00 +01:00
github-actions[bot]andCrowdin Bot c3418cbe1f New Crowdin translations by Github Action (#5990)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-13 10:57:22 +01:00
Hariom Balhara 3a641e0bc1 Feature/Embed/Add Embed ui instruction config to hide event type description (#5951)
* Add embed ui instruction to hide event type description

* Add EventTypeDetails option in Embed Snippet Generator
2022-12-13 07:23:26 +00:00
Carina Wollendorfer b7fcf6ade1 fix missing space in email (#5986) 2022-12-12 12:51:27 -07:00
github-actions[bot]andCrowdin Bot 53d160e495 New Crowdin translations by Github Action (#5984)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-12 14:24:10 +01:00
sean-brydon df4df6846b Required radio button (#5985) 2022-12-12 14:18:04 +01:00
Carina WollendorferandCarinaWolli c1bbb9b139 Fixes SMS not sending for country codes not supporting alphanumeric sender IDs (#5969)
* don't use alphanumeric sender ID for not support country codes

* fix import

* rename file

* fix spelling mistakes

* add twilio phone number to .env file

* add SENDER_ID to env variable and create function to get sender id

* add NEXT_PUBLIC_SENDER_ID env variable

* remove not needed Cal fallback

* fix readme

* code clean up

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-12 12:00:15 +01:00
Hariom Balhara 56c07ec351 Hide Branding in Embed. (#5975) 2022-12-12 08:37:36 +00:00
Hariom Balhara 3080b47aec Theming shouldnt depend on plan now as everyone is pro now (#5976) 2022-12-12 08:25:59 +00:00
c1e23bfdc6 fix/duplicate-event-types (#5888)
* Add new modal for duplicate and trpc action for it

* Adding event type duplication validations

* Fix type errors and id not being removed from rest obj

* Fix test for duplicating event

* Fix test for duplicating event

* Add connect to event type workflows

* fix button for adding new events

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-10 15:48:26 -07:00
e7ff5508d7 New Crowdin translations by Github Action (#5966)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-10 22:46:06 +01:00
Nafees Nazik 5850d227ee fix: render upgrade banner above ImpersonatingBanner (#5968) 2022-12-10 21:45:35 +00:00
sean-brydon ede987ba19 Fixing branding toggle (#5952)
* Fix form state not saving

* Always show false on toggle if you dont have an active team

* Correct filter
2022-12-09 22:33:49 +00:00
zomars bc0d83164c v2.3.8
release.yml / release (push) Canceled after 0s
2022-12-09 10:43:49 -07:00
zomars 30f0b080c5 SIU 2022-12-09 10:43:19 -07:00
Leo Giovanetti ee39ea316f Fixing app store search input (#5960) 2022-12-09 14:10:54 -03:00
Hariom BalharaandPeer Richelsen fbc2708259 Hotfix/Google Meet must be globally installed (#5948)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-09 12:51:23 +00:00
9bb83aba7e removed license consent (#5931)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-09 11:05:42 +00:00
Omar López 3a67e5617e v2.3.7
release.yml / release (push) Canceled after 0s
2022-12-08 22:44:49 -07:00
f401aa7669 Harsh/fix avail time order (#5900)
* fix: 404 page for undefined events

* remove prev code merge

* add: sorting in available durations

* fix: sort only once

* remove: console logs

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-09 00:16:08 +00:00
a6e0678e59 feat: use next/script tag for inline scripts (#5938)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-08 23:39:15 +00:00
Omar López e832015f26 Deprecates user plan (#5942)
* Remove isMissingSeat

* Removes user plan

* Deprecates User Plan

* Updates website

* Update eventTypes.tsx
2022-12-08 16:20:24 -07:00
zomars 5e86881e15 Adds missing package 2022-12-08 16:17:04 -07:00
zomars 3b71f98460 Submodule sync 2022-12-08 15:00:29 -07:00
144d57adb2 fix: make banner responsive and cover entire app (#5934)
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>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-08 18:42:55 +00:00
github-actions[bot]andCrowdin Bot e1f1c15318 New Crowdin translations by Github Action (#5932)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-08 19:31:16 +01:00
d88b1b2f8f remove {{appName}} in translation (#5924)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-08 18:28:22 +00:00
2e61c15f2c fix: check input is not undefined (#5927)
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>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-08 18:16:04 +00:00
Hariom Balharaandkodiakhq[bot] df73473c7f Avoid redirect in cancellation and visit from Bookings list (#5928)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-08 17:32:44 +00:00
github-actions[bot]andCrowdin Bot 7167973afd New Crowdin translations by Github Action (#5920)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-08 15:12:24 +01:00
Omar López 3c17bc2d1a Fixes for avatar payloads (#5921) 2022-12-08 09:50:03 +00:00
a9a295dc54 Admin apps UI (#5494)
* 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

* Added migration to keep current apps enabled

* Update apps.tsx

* Fix bug

* Add keys schema to Plausible app

* Add appKeysSchema to zod.ts template

* Update AdminAppsList.tsx

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>
2022-12-07 14:47:02 -07:00
+2 21dd1f4e95 [CAL-462] Team settings dropdown menu should be open when you navigate from Teams (#5868)
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart_bot <gitstart_bot@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart_bot <gitstart_bot@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
2022-12-07 21:35:41 +00:00
zomars 5d775bd4a3 v2.3.6
release.yml / release (push) Canceled after 0s
2022-12-07 14:02:24 -07:00
Nafees Nazik 4e02b22472 fix: add optimistic update for disabling forms (#5547) 2022-12-07 13:55:41 -07:00
4686d3ff48 Improve multilingualism and fix search routes (#5334)
* fixed search routes

* made static text multilingual and fixed german translations

# Conflicts:
#	apps/web/components/availability/Schedule.tsx

* delete empty file created by fixing merge conflicts

* fixing TextField placeholder by passing attendeeName and fixed json variable

* Using useLocal to modify static text to make it multilingual, and passing the correct variables for brand and mail

* seperated whitelabel and improved translations

* added missing translation

* added missing translation for webhooks

* Updated AdminAppsView with dynamic translations.
Added translations for german and english files only.

* changed back to one liner

* updated german and english translations for impersonation.tsx

* updated german and english translations for impersonation.tsx and users.tsx. added missing german translation for timeformat_profile_hint

* updated german and english translations for team-billing-view.tsx

* updated german and english translations for LicenseRequired.tsx

* updated routes for profile and avatar

* yarn.lock updated from newer changes

* Revert " yarn.lock updated from newer changes"

This reverts commit efd9a90bf774371a331861e4fb441ab4f4d0b7fd.

* sanitize dangerouslySetInnerHTML to prevent xss attacks

* tried to fix window title flicker

* changed ssdInit to ssrInit for getServerSideProps. Serverside translation works but current route still set as a window title

* flicker with route in window title is caused here. It is not necessary to check if isPublic and session is false because it already gets checked in useRedirectToLoginIfUnauthenticated hook.

* fixed window title translation flicker for availability page

* fixed window title translation flicker for teams page

* fixed window title translation flicker for workflow page

* fixed error that div may not be rendered within p element

* fixed window title translation flicker for booking page

* fixed window title translation flicker by adding getServerSideProps

* Only set HeadSeo if an page title exists. If window title is set by the Meta component, shell is causing a flicker because it overwrites the title which is set by Meta. It is a problem especially for settings pages.

* fixed window title translation flicker by adding the Meta component to the skeleton

* fixed condition

* removed condition and added withoutSeo for settings pages

* using translations for create team page further fixed flicker for window title

* fixed flicker for window title for event-type creation page

* fixed flicker for window title for availability creation page

* fixed flicker for window title for sso page

* updated conferencing en translation

* added meta.CTA back it was mistakenly removed

* fixed flicker for workflows page

* fixed missing variable

* Update packages/ui/v2/core/Shell.tsx

* Delete index.tsx

* Update sso.tsx

* Updates subdmoules

Co-authored-by: maxi <maximilian.oehrlein@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-07 13:53:44 -07:00
zomars 02e9d8d45f Sync submodules 2022-12-07 13:40:37 -07:00
Udit Takkar 7ce5096f13 fix: resetting input on cancel and refactoring input fiedl (#5522)
* fix: resetting input on cancel and refactoring input fiedl

* feat: add username field in /usersettings

* chore: remove unused

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

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 13:15:15 -07:00
Udit Takkar 83e51e9b9d fix: use trim in zod (#5915)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 13:12:53 -07:00
Udit TakkarandPeer Richelsen dca3278c40 fix: zoom video (#5910)
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>
2022-12-07 13:12:26 -07:00
Udit Takkar 7501635fbf fix: delete use when 2fa is disabled (#5899)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 13:12:14 -07:00
Peer Richelsen 80c96ea5ac remove "Add bugs to Bug Hunting" workflow (#5901)
not needed anymore
2022-12-07 13:09:01 -07:00
Hariom BalharaandPeer Richelsen 8c001e0d34 Fix reschedule embed (#5861)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 13:08:34 -07:00
Joe Au-Yeungandalannnc bf275497a2 MS Teams use joinWebUrl from response body (#5886)
* Add joinWebUrl to event

* Remove console.log

Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-07 12:56:15 -07:00
6e351a4b20 fix/premium-username-flow (#5815)
* Removes user plan logic for stripe sub

* Update hardcoded price

* Removed checkoutSessionId metadata and fix for trpc query

* Remove or comment team billing unused code

* Add todo note

* Fix type checks

* Change function that was renamed

* Remove duplicated test

* remove plan when creating user on test

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 12:55:47 -07:00
github-actions[bot]andCrowdin Bot 5360cea3bf New Crowdin translations by Github Action (#5919)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-07 20:49:45 +01:00
+2 2f36985462 /availability - Add ability to set as default in the dropdown (#5717)
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: gGh00sTt <coolmagnas@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: gGh00sTt <coolmagnas@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2022-12-07 12:35:55 -07:00
6b26e9a7de Harsh/fix timeformat hint (#5918)
* fix: 404 page for undefined events

* remove prev code merge

* fix: timeformat profile hint

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <root@pop-os.localdomain>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 18:22:43 +00:00
github-actions[bot]andCrowdin Bot d7655f0521 New Crowdin translations by Github Action (#5914)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-07 19:10:15 +01:00
sean-brydonandLeo Giovanetti f4ed345a87 Fixes disable branding for teams and users (#5913)
* Adding new nextauth object

* Use correct brand hidden values

* Move check to getprops instead of a function

* Update apps/web/components/booking/pages/AvailabilityPage.tsx

* Update apps/web/components/booking/pages/AvailabilityPage.tsx

* Update apps/web/pages/[user]/[type].tsx

Co-authored-by: Leo Giovanetti <hello@leog.me>

* Update apps/web/pages/api/auth/[...nextauth].tsx

Co-authored-by: Leo Giovanetti <hello@leog.me>

* Update apps/web/pages/api/auth/[...nextauth].tsx

Co-authored-by: Leo Giovanetti <hello@leog.me>

Co-authored-by: Leo Giovanetti <hello@leog.me>
2022-12-07 15:04:04 +00:00
SumedhandPeer Richelsen c3122c82e4 fix/cant-create-team-name-taken (#5911)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-07 14:50:54 +00:00
Udit Takkar cc9b49131e fix: add toast and fix delete action (#5912)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-07 15:37:20 +01:00
github-actions[bot]andCrowdin Bot 190db1a0f6 New Crowdin translations by Github Action (#5903)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-06 23:25:53 +01:00
Cory Dransfeldt bd48a72e99 [Feature] Adds support for Plausible analytics to app store (#5906) 2022-12-06 21:19:16 +00:00
Nafees Nazik a6836a1488 fix: use the new docker compose command if it exists (#5852) 2022-12-06 11:41:50 -07:00
2b67ea9635 Harsh/fix 404 page for undefined events (#5700)
* fix: 404 page for undefined events

* Update apps/web/pages/[user]/[type].tsx

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

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2022-12-06 15:04:34 +00:00
Udit Takkar a9117c2d36 fix: use tooltip portal (#5889)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-06 13:57:02 +00:00
+1 10d3b5f1b9 Remove the back arrow (#5402)
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@gitstart.com>
2022-12-06 13:02:58 +00:00
Peer Richelsen b757cb6ba1 added new teams empty screen (#5847)
* added new teams empty screen

* replaced svg background with smaller jpg

* nit

* nit

* nit

* nit

* show old emptyscreen for self-hosters

* nit
2022-12-05 15:20:33 -07:00
5968e0aa19 added EventReservationSchema to success page (#5777)
* added EventReservationSchema to success page

* removed duplicated of yarn.lock

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 21:35:44 +00:00
zomars a06fffc4ec Adds missing translation 2022-12-05 14:24:09 -07:00
zomars 3312c84e7c Update turbo.json 2022-12-05 14:17:35 -07:00
Nafees NazikandPeer Richelsen d86710d5e9 fix: remove unnecessary title (#5853)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 21:11:10 +00:00
Udit Takkar 2d8e65c160 fix: ui of calendar item (#5879)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2022-12-05 20:39:52 +00:00
Udit TakkarandPeer Richelsen f74eaf7cb3 fix: increase edit location height (#5882)
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>
2022-12-05 20:18:45 +00:00
bcfd57c953 Bugfix/payment options (#5805)
* removed locale to use automatic detection in teams/lib/payments.ts

* removed payment_method_types to manage payment methods in the Dashboard

* added customer_update and automatic_tax to apply tax rates to payments

* added allow_promotion_codes to allow promotion codes

* removed multiple usages of `allow_promotion_codes` in payments.ts

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-05 13:02:59 -07:00
c38588b93d New Crowdin translations by Github Action (#5842)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 20:59:12 +01:00
alannnc 32ed43574d Fix input parse to number (#5884) 2022-12-05 20:57:33 +01:00
38a5a22b9e Harsh/fix bookings name (#5848)
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <root@pop-os.localdomain>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 17:41:40 +00:00
Sascha FoersterandPeer Richelsen 2120e301c3 Update german translations (#5854)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 17:29:19 +00:00
Bailey Pumfleet dd0aec4e97 Fix Kbar translation (#5867) 2022-12-05 14:43:31 +00:00
Peer Richelsen 3815b86258 added norwegian to i18n (#5866) 2022-12-05 13:58:00 +00:00
Hariom BalharaandPeer Richelsen 7a6545bd03 Wordpress App Files cleanup (#5858)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 13:42:38 +00:00
Udit TakkarandPeer Richelsen 11f467af5d fix: toggle (#5865)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-05 13:28:21 +00:00
0930cf50b6 BugFix: Link based Apps installation - Opens 2 tabs instead of 1 (#5859)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-05 14:13:01 +01:00
c37cddd0b7 feat: add each app from the app store to KBar (#5724)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-12-05 12:13:31 +00:00
09253f83bf [CAL-463] Clicking Members from event types screen should take you to the members page (#5857)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: gitstart-app[bot] <57568882+gitstart-app[bot]@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Benini <matheus_benini@hotmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-05 12:12:37 +00:00
Leo GiovanettiandBailey Pumfleet 1cdcebac01 Requires Confirmation Threshold (#5825)
* WIP + react errors fixed

* Booking and Availability page treatment

* Update BookingPage.tsx

Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-12-05 12:12:14 +00:00
sean-brydon 58270fa653 Team impersonation (#5863)
* Re Implement frontend logic to impersonate team members

* Refactor dialog + correct disable toggle.

* fix translation

* Update packages/features/ee/impersonation/lib/ImpersonationProvider.ts
2022-12-05 12:03:36 +00:00
Peer Richelsen 1a26a254ab added team subdomain to whereby validation (#5850) 2022-12-03 19:32:40 +00:00
Udit Takkarandalannnc a36d15894d fix: user infer props and remove avatarSSr from onboarding (#5549)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-03 19:20:18 +00:00
Sumedh cf077fd33c fix/sidebar-profiledropdown-wired-hoverstate (#5832) 2022-12-03 15:12:12 +00:00
sean-brydonandPeer Richelsen 0c979d3604 Fix imports + buttons (#5831)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-03 14:53:07 +00:00
zomars 33bc786bb3 API scripts 2022-12-02 15:25:13 -07:00
zomars a7f16462db v2.3.5
release.yml / release (push) Canceled after 0s
2022-12-02 13:33:41 -07:00
zomars 2c7fcbcf51 hotfix: 404 for mixed case usernames 2022-12-02 13:32:56 -07:00
Syed Ali ShahbazandAlex van Andel d3e4d26fa5 fixes UTC consideration in CalDAV (#5827)
* fixes UTC consideration

* typecheck fix

* code quality

* comment location

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-02 19:46:38 +00:00
Carina WollendorferandCarinaWolli 3be6c05722 Fixes some translation issues (#5841)
* fix french translation for rescheduled email

* fix translations for team invite

* fix rescheduling email in german

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-02 17:56:48 +00:00
Carina WollendorferandCarinaWolli e293e590bd remove if and at to query (#5838)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-02 13:43:07 +00:00
+1 d8c1c103a1 [CAL-103] All Bookings show up under Today and Today should be uppercase (#5155)
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-02 10:08:12 +00:00
Joe Au-YeungandAlan 19d00e691c Fix attendees on a booking with seats and payments, navigate to the payment page. (#5788)
* Payment booking logic to seats

* Pass email to success page from payment page

* Fix type error

Co-authored-by: Alan <alannnc@gmail.com>
2022-12-02 00:12:06 +00:00
78aa4aecef Settings: Tab item line height fix && Stop x-axis overflow of settings tab (#5699)
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-01 23:09:14 +00:00
3ab002e547 5444 cal 339 radio option in additional questions on public booking page (#5804)
* Use field array intro

* WIP - form submitting wrong form

* WIP with fake useFormHook

* WORKING! OMG

Co-authored-by: Alex <alex@cal.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>

* Booking Page styling

* Fix duplicate fields

* Radio string

* Type error

* Linting errors

* Remove unused duplicate file

* Fixed user related type error

* remove log

* Remove console logs

* remove console log

* fix dark mode text and comment style

Co-authored-by: Alex <alex@cal.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-01 21:53:52 +00:00
Nafees Nazikandalannnc 7f461bc275 fix: padding (#5808)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-01 13:30:57 -07:00
Carina WollendorferandCarinaWolli 571176102e add smsReminderNumber to webhook payload (#5823)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-01 12:51:59 -07:00
github-actions[bot]andCrowdin Bot 8f1d3fbc50 New Crowdin translations by Github Action (#5816)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-01 15:42:24 +00:00
Joe Au-YeungandAlex van Andel 5e88ad50ea If no location is set, default to Cal Video (#5812)
* Default to Cal Video

* Show web conferencing details to follow on success page

* Show web conferencing details to follow on success page

* Show web conferencing details to follow on success page

* Type fixes

* Pass tests

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-01 10:20:01 -05:00
Alex van Andel 056406bdd9 v2.3.4
release.yml / release (push) Canceled after 0s
2022-12-01 11:18:18 +00:00
Alex van AndelandOmar López 58af5a18a4 Moved some files around & made some fixes (#5811)
* Moved some files around & made some fixes

* Update apps/web/pages/event-types/index.tsx

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

* Update apps/web/pages/apps/installed/[category].tsx

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

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

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

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

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

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

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

Co-authored-by: Omar López <zomars@me.com>
2022-12-01 00:59:27 +00:00
zomars 90b6581d76 Allows promo codes for team subscriptions 2022-11-30 17:36:04 -07:00
sean-brydon b33dcc8841 Weird (#5818) 2022-11-30 17:11:59 -07:00
d951a5b872 Allows brand customization (#5329)
* adjustments for each language json file:
- changed every Cal or Cal.com with a variable to make it possible to change that with a custom brand
- fix and renamed  ATTENDEE with attendeeName

* added two new variables for appName and support mail address. so everybody can change it via env

* changed static Cal or Cal.com with new defined constants

* Using useLocal to modify static text to make it multilingual, and passing the correct variables for brand and mail

* adding new readable variables for brand, website domain and mail address

* fixed search routes

* made static text multilingual and fixed german translations

* Revert "fixed search routes"
moved changes in another pr
This reverts commit e6ba11a1ec7821d8c16c502d0357f6d5fcdb1958.

* revert non whitelabel changes and moved it into another pr

* revert attendeeName fix

* reverted translation fixes and moved them in another pr

* changed back to "Cal.com Logo"

* changed back to "https://console.cal.com"

* added new env variable for company name and replaced some domainName variables in language files

* changed default for COMPANY_NAME to Cal.com, Inc.

* changed Cal.com to APP_NAME for mail templates

* Dropped website domain in favor of app name

* Update .env.example

* Apply suggestions from code review

* Code review feedback

* Delete App.tsx

* Update packages/ui/Kbar.tsx

* added meta.CTA back it was mistakenly removed

* updated add members test

Co-authored-by: maxi <maximilian.oehrlein@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2022-11-30 14:52:56 -07:00
Nafees NazikandPeer Richelsen 7122a9cfbf fix: padding (#5809)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-30 21:41:29 +00:00
f1b92c49fc App Store Search (#5704)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2022-11-30 20:51:44 +00:00
Leo Giovanettiandkodiakhq[bot] ef3ad36c2c Fixed listing formatting and save for custom inputs (#5795)
* Fixed formatting and save

* Additional tweaks

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-30 11:53:28 -07:00
072ce452b7 Catch the error that's thrown when no api_key is found (no daily) (#5696)
* fix/event-type-location-select (#5692)

* Fixed dialog components usage

* Fix styles

* Catch the error that's thrown when no api_key is found (no daily install)

Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-30 16:44:20 +00:00
Leo Giovanetti 0e0940f5f0 Fixing button/select styles (#5810) 2022-11-30 16:13:20 +00:00
Jeroen Reumkens 310936125d Changed select export to use v2 instead of v1. (#5715) 2022-11-30 08:42:45 +00:00
Peer Richelsen a9df3309b9 added wordpress plugin (#5168) 2022-11-29 21:57:26 +00:00
148d658f14 fix: update new team cancel button url (#5520)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-29 21:38:54 +00:00
Nafees NazikandPeer Richelsen 242d571d7d fix: missing rounded corners in team member settings (#5789)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-29 21:26:01 +00:00
dff49ec28a Rename /success to /manage and fix link in calendar event (#5719)
* fix need to make changes link in calendar event

* change /success link to /manage

* delete success.tsx file and use next.js rewrites

* fix e2e tests

* remove not needed waitForNavigation

* fix e2e tests

* rename manage?uid to booking/uid

* fix rewrite

* remove not needed export

* fix rescheduling e2e tests

* Minor fixes/cleannup

* Update BookingPage.tsx

* Moves cancel page to rewrite

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-11-29 20:27:29 +00:00
Leo Giovanetti 0c746ec10f Fixing add button (#5780) 2022-11-29 19:18:11 +00:00
Leo Giovanetti 901f54ca3a Fixing create event (#5781) 2022-11-29 11:47:08 -07:00
alannnc 16fda5f599 Enables require confirmation after payment its made (#5671) 2022-11-29 18:28:02 +00:00
d3e9adf382 allow cancelling and rescheduling for not confirmed bookings (#5770)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-11-29 16:03:39 +00:00
cf559e0726 New Crowdin translations by Github Action (#5753)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2022-11-29 15:51:24 +00:00
Udit Takkar 8447b1bbd5 fix: use shouldDirty flag (#5775)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

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

* Missing translation

* Default duration clears correctly

* Fixing hydration error

* Applying feedback

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-28 18:14:01 +00:00
Jeroen Reumkensandkodiakhq[bot] e16d29e916 Added optional chaining to embed iframe using window object, where objects were potentially undefined. (#5727)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-28 09:54:14 -07:00
zomars 0e89fb9f6a Update release.yml 2022-11-28 09:07:30 -07:00
zomars 43fb410a28 v2.3.2
release.yml / release (push) Canceled after 0s
2022-11-28 09:02:55 -07:00
b8bbfe8a3f fix: ui of reschedule dialog (#5691)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-28 08:52:42 -07:00
Leo GiovanettiandAlex van Andel 1f74fc394a Fixing calWarnings error on reschedule (#5711)
* Fixing calWarnings error on reschedule

* Update packages/core/CalendarManager.ts

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

* Update packages/core/CalendarManager.ts

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

* Going back on potential fix

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

* remove not needed waitForNavigation

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

* remove prev code merge

* change: z-index value of banner

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

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

* delete old cancel success page

* fix e2e tests

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

* add new editor to email body

* fix line height

* remove not needed styles

* allow adding html tags + small refactoring

* fix error when input is empty

* styling of add variable dropdown

* fix bg on focus

* small design fixes

* improve design

* show error message for no input

* text editor only for email body

* remove test button

* code clean up

* code clean up

* code clean up

* fix that added variables weren't saved

* fix import

* first version of editor

* connect reminder body of form with new editor

* add pencile for link-editor

* remove TreeViewPlugin

* clean up css file

* code clean up

* add text indent for list

* improve editor design

* fix style

* slow resizing of editor

* fix that new data was removed when leaving tab

* dont allow italic and bold at same time

* code clean up

* fix type error

* code clean up

* code clean up

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

* add add variable dropdown

* show error message if email body is empty

* add white-space pre-wrap

* fix import

* prettier

* fix add variable dropdown for mobile

* move dependencies to correct package.json file

* remove html-to-text

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

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

* fix: type errors and role value bug

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

* chore: remove logs

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

* chore: change option to admin

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

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

* fix: disable clickable profile on private booking pages

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

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

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

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

* fix: invalidate after dsubmission

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

* Fix isDirty state for appearance settings

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

* fix rescheduling bug

* Change array iteration to only run for best scenario

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

* chore: remove logs

* fix: remove required location

* fix: make locations option

* fix: get location

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

* Fix location being overrided by default

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

* chore: remove logs

* fix: remove required location

* fix: make locations option

* fix: get location

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

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

* Upgraded next-auth to v4.17

* Latest known to work version of next-auth

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

* Type fixes

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

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

* add removed comment

* take rescheduled opt ins into account

* code clean up

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

* Fix app store seed config json

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

* updated workflow pages imports to remove v2 from path.

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

* Removed v1 list component.

* Fixed event workflows tab path.

* Fixed import path for button in sandbox page.

* Cleanup and type fixes

* Making explicit indexes

* UI import migrations

* More import fixes

* More import fixes

* Submodule sync

* Type fixes

* Build fixes

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

* warnings and errors, and working app

* Refresh token now usable

* Correcting env.appStore.example

* Reverting changes that will come from Sendgrid App PR

* Resetting with main

* Renaming all othercalendars

* Fixing types

* Renaming leftovers

* More renaming stuff

* Format readme

* Adds prettier override for website wordlist

* Omit salesforce app in this PR

* Revert "Omit salesforce app in this PR"

This reverts commit 22c09fe53578fc37293027ef3667e1bdf2d6c6e7.

* Update yarn.lock

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

* warnings and errors, and working app

* Refresh token now usable

* Correcting env.appStore.example

* Reverting changes that will come from Sendgrid App PR

* Resetting with main

* Renaming all othercalendars

* Fixing types

* Renaming leftovers

* More renaming stuff

* Format readme

* Adds prettier override for website wordlist

* Omit salesforce app in this PR

* Cleanup

* Update AppSettings.tsx

* Revert "Cleanup"

This reverts commit 41f94c52c3a34ce8ae6047ce9d4c187722343d7e.

* Update yarn.lock

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

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

* added another screenshot

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

* Added animations to buttons.

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

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

* Undo disabling of api jest tests.

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

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

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

* Only apply buffer limits to That event type

* Correctly get eventtypeId in source

* Add comment

* Move to i188n strings

* Wrap string to be translated

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

* code clean up

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

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

* fix: preload translations

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

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

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

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

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

* Applying app-store-cli + impl

* Fixing types

* Adding features to readme

* Fixing unit tests

* A few last tweaks regarding UX and env vars

* Applying feedback

* Using calcom icons

* Renaming and applying feedback

* Testing user/type page fix

* Standarizing Sendgrid client usage

* Removing types

* Reverting CloseCom changes

* Stop relying on sendgrid client pkg

* Fixing button and more reverting closecom changes

* Revert "Stop relying on sendgrid client pkg"

This reverts commit dd61851572a17a1e4051b133683af85c934bc2d0.

* Revert "Removing types"

This reverts commit 1ec5ed8de2f3139bbe84f867f229bc5759256806.

* Is this it?

* Standardizing apis

* Fixing path

* Fixing throwing errors the standard way

* Stop relying on getInstalledAppPath

* Removing seemingly troubling code

* Returning error and avoiding any outer reference

* Revert "Returning error and avoiding any outer reference"

This reverts commit 7d32e30154423c95f54ebae81a76ab16a1c7bc94.

* Revert "Removing seemingly troubling code"

This reverts commit eaae772abcd04c8f046e4960116f42c5aaf87adf.

* Revert "Stop relying on getInstalledAppPath"

This reverts commit bcc70fc337bbe7fb5e74609abaeee7cd3ede90a3.

* Revert "Fixing throwing errors the standard way"

This reverts commit bb1bb410fac6f8c6ad14c3163a8433d125f7a885.

* Revert "Fixing path"

This reverts commit a7bd83c4fb7597594d0470cb530378c826b45481.

* Revert "Standardizing apis"

This reverts commit 0258a182298af3ebad321854ef4f34a65f4c700a.

* Revert "Is this it?"

This reverts commit 70b3f7b98e3003dfa225dc539e02a1e17abdd840.

* Converting APIs to legacy style

* Missing reverted CloseCom test mock

* Needed for the renaming

* Reverting Closecom and yarn unneeded changes

* Ununsed type

* Testing rearranging exports

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

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

* Standardizing APIs

* Fixing wrong toast message on app page

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

* fix: handle login in handleZoomResponse

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

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

* fix: use slug instead of url

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

* Update TeamListItem.tsx

* Update payments.ts

* Copy updates and mobile style fixes

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

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

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

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

* Minor changes to the succes.tsx and tests

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

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

* design fixes

* fix UI for recurring bookings

* fix success page for recurring events

* fix cancelling recurring events

* fix design of successful cancellation page

* fix email redirect + design fixes

* remove old cancel page

* fix v2 design for text area

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

* redirect form old to new cancel page

* fix success page for recurring events

* rename to allRemainingbookings

* fix old cancel redirect

* fix recurring cancel link in email

* fix redirect when cancelling one recurring booking

* remove reschedule for recurring events in email

* create queryschema for cancel success page

* scroll down to bottom if cancel

* add cancellation reason

* fix tests

* fix old /cancel component

* code clean up

* Uses URL query as cancellation state

Also fixes string to boolean inference

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

* Replaced remaining v1 components.

* Removed todo.

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

* Undo update submodule.

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

* wip

* chore: remove unused import

* feat: top banner component added

* feat: top banner stories added

* fix: icon bug

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

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

* Apply suggestions from code review

* Export fixes

* Feedback

* Type fixes

* Fixes

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

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

* Minor changes to the succes.tsx and tests

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

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

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

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

* Fixed skeletons for teams and workflows.

* Fixed skeleton for workflow detail page.

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

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

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

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

* fix type error

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

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

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

* Update packages/lib/convertMinimumBookingNoticeToMinutes.ts

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

* added MINUTES_IN_HOUR, MINUTES_IN_DAY, and HOURS_IN_DAY as exportable consts

* Fix some comparison, mobile styles and const usages

* minimumBookingNotice values will never be floats

* fixed issue with minbookingnotice not updating properly in prisma

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

* remove yarn lockfile

* Revert "remove yarn lockfile"

This reverts commit fefc24c80269f5445c8eb7a640e2816d7d8b0407.

* Revert "Revert "remove yarn lockfile""

This reverts commit c8cc4d3c0eddb2c91d077d2277ad5ea10315ec95.

* Replaced currentDurationType export with state passed down from parent

* Improvements for duration notice type logic

* Undo adding submodule updates

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

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

* Update packages/lib/findDurationType.ts

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

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

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

* Update packages/lib/convertToNewDurationType.ts

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

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

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

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

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

* remove passing in as object

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

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

* Update packages/lib/convertToNewDurationType.ts

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

* Delete convertMinimumBookingNoticeToMinutes.ts

* Syntax fix

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

* remove button disabled prop

* update mutation to latest trcp style

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

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

* Apply suggestions from code review

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

* Linting

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

* fix: recurring text

* fix: logo size and font

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

* fix: dropdown trigger ui bugs

* Update packages/ui/Dropdown.tsx

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

* add sender to twilio from

* added missing sender

* add migration

* fix design of add action dialog

* add cal as sender when creating new workflow

* fix type errors

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-11 15:01:17 +00:00
Alex van Andel ef3e7fae20 v2.1.5
release.yml / release (push) Canceled after 0s
2022-11-11 11:46:17 +00:00
Hariom Balharaandkodiakhq[bot] bb7815464c Feature/Routing-Forms/Reporting (#5375)
* Add very basic reporting logic

* Support more logical operators

* Add tests

* Fix types

* Add playwright test for reporting

* Fix table UI

* Remove only

* Better name variable

* Increase max_old_space to allow tests to run without reaciing limit

* Update test name

* Reuse getServerSideProps

* Variable renaming

* PR Suggestions

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-11 09:57:44 +00:00
Alex van AndelandOmar López acb6f292ec Bugfix/revert e5ba8616 (#5479)
* Revert e5ba8616

* Revert "Reverted Sentry for debug purposes (#5425)"

This reverts commit 4466c4ad4b.

Co-authored-by: Omar López <zomars@me.com>
2022-11-11 09:47:11 +00:00
0b2250caf9 fix: routing form edit button (#5466)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2022-11-11 15:01:13 +05:30
Julius Marmingeandzomars db7c3fb52f feat: trpc v10 migration (#5332)
* migrate router

* createTRPCReact

* frontend 1

* random format change

* frontend 2

* withQuery

* form router

* TS-ERROR: proxy on utils-client

* inferance

* ssg

* reuse

* trpc rc4

* Apply suggestions from code review

* skip test

* move skip one level up

* whops

* rc 6 with new setData func

Co-authored-by: zomars <zomars@me.com>
2022-11-10 23:40:01 +00:00
6d67808627 Team billing (#5453)
* WIP teams billing page

* WIP

* Create settings page

* Remove unused imports

* Create stripe customer on team creation

* Add Stripe ids to team record

* Add Stripe price ids for team to .env

* Create & delete Stripe customers

* Add string

* Merge branch 'main' into v2/teams-billing

* Create checkout session when creating team

* Create webhook to update team with Stripe ids

* Add Stripe migration files

* Move deleting team from Stripe under ee

* Some cleanup

* Merge branch 'v2/teams-billing' of https://github.com/calcom/cal.com into v2/teams-billing

* Small clean up

* Link to team's portal page

* Fix types

* Fix type errors

* Fix type errors

* Fix type error

* Delete old files & type fixes

* Address feedback

* Fix type errors

* Removes team creation modal

* WIP

* Removed billing frequency from team creation

* Add Stripe check for delete team customer

* Merge branch 'v2/teams-billing' of https://github.com/calcom/cal.com into v2/teams-billing

* Add high level form to create new team

* WIP

* Add new team to form

* Validate for invited members

* Add translations

* WIP

* Add validation for team name

* Add validation to team slug

* Clean up

* Fix type error

* Fix type errors

* WIP

* Abstract invite members function

* Add subscription status column

* Hide pending teams from settings

* Send email on paid subscription

* WIP

* Sync packages

* Add team subscription cols to schema

* WIP

* Matches locks vite version to <3

* Removed subscriptionStatus

* WIP

* Fix warning

* Query optimizations

* WIP

* Cleanup

* Wip

* WIP

* Runtime error fixes

* Cancellation fixes

* Delete team fixes

* Cleanup

* Type fixes

* Allows to check memebership in getTeamWithMembers

* Adds team creation tests

* Cleanup

* Cleanup

* Restored change

* Updated copy

* Moved component

* Cleanup

* Fix team members view

* Cleanup

* Adds failsafe for skipping publishing on update

* Cleanup

* Feedback

* More feedback

* Cleanup

* Cleanup

* Feedback

* Feedback

* Feedback

* Adds edge-case for slug conflicts

* Feedback

* e2e fixes

Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-10 13:23:56 -07:00
Nafees Nazikandkodiakhq[bot] 557b57a261 fix: floating pop up button embed modal size (#5415)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-10 13:09:19 +00:00
31875f7535 Test Preview for routing forms (#5436)
* add first version of testing routing forms

* small design changes

* use new shared component

* readd deleted code for showing result route

* add form validation for required fields

* design fixes

* add scroll

* fix design of select

* use old dialog with correct overflow behaviour

* code clean up

* remove unused import

* fix mobile view

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-10 12:58:07 +00:00
29c4efe4a8 Delete slack app (#5462)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-10 11:54:15 +00:00
Udit Takkarandkodiakhq[bot] 7bda15aaa3 fix: ui of webhook segment (#5452)
* fix: ui of webhook segment

* feat: use toggle group component

* fix: default bug

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-10 11:38:40 +00:00
4176086ce0 Removed v1 component split for event types component (#5399)
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-10 11:23:55 +00:00
Hariom Balharaandkodiakhq[bot] 04e62a10e6 In selfhosted, calvideo isnt default and thus an event can be created without location. Handle that (#5457)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-10 11:07:16 +00:00
Carina WollendorferandCarinaWolli 0ff38dece2 FIx button text (#5456)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-11-10 09:07:26 +00:00
Hariom Balhara 8416b76bf6 Fix flakiness (#5454) 2022-11-10 07:45:10 +00:00
zomars c3a427346d Matches locks vite version to <3
# Conflicts:
#	yarn.lock
2022-11-09 16:42:49 -07:00
Udit Takkar a73b3fcaed fix: use Intl to format month name (#5449) 2022-11-09 19:14:01 +00:00
Alex van Andel 25d684ed3e v2.1.4
release.yml / release (push) Canceled after 0s
2022-11-09 16:29:23 +00:00
Alex van Andel 195cb84a8b Disable ADMIN role when password requirements aren't met (#5445) 2022-11-09 16:23:39 +00:00
4466c4ad4b Reverted Sentry for debug purposes (#5425)
* Reverted Sentry for debug purposes

* Downgraded yarn.lock

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-09 16:10:31 +00:00
4b532646cb fix: back url (#5414)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-09 15:57:23 +00:00
Udit Takkar 6d42fdb71c chore: fix typo (#5432) 2022-11-09 15:45:42 +00:00
Bailey Pumfleet 1b23671812 Update password length requirement to 8 characters (#5438) 2022-11-09 12:42:30 +00:00
Leo GiovanettiandAlex van Andel 7d1fb7c659 Fixing count and redundant checks for recurring (#5426)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-08 20:59:44 +00:00
zomars 97b99deb5d Adds release action 2022-11-08 13:32:18 -07:00
zomars 4803c956dc DB previews take 2 2022-11-08 13:27:55 -07:00
zomars 8ff5d476d9 Sync submodules 2022-11-08 10:48:54 -07:00
1222 changed files with 37213 additions and 34658 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"
}
+10 -1
View File
@@ -11,6 +11,10 @@
# - ZOOM
# - GIPHY
# - VITAL
# - ZAPIER
# - LARK
# - WEB3
# - SALESFORCE
# - APP STORE **********************************************************************************************
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
@@ -88,7 +92,7 @@ VITAL_REGION="us"
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/zapier/README.md
ZAPIER_INVITE_LINK=""
# - LARK
# - LARK
# Needed to enable Lark Calendar integration and Login with Lark
# @see <https://open.larksuite.com/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/g>
LARK_OPEN_APP_ID=""
@@ -100,4 +104,9 @@ LARK_OPEN_VERIFICATION_TOKEN=""
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/rainbow/README.md
ALCHEMY_API_KEY=""
INFURA_API_KEY=""
# - SALESFORCE
# Used for the Salesforce (Sales Cloud) app
SALESFORCE_CONSUMER_KEY=""
SALESFORCE_CONSUMER_SECRET=""
# *********************************************************************************************************
+15 -10
View File
@@ -7,14 +7,13 @@
# - E-MAIL SETTINGS
# - LICENSE *************************************************************************************************
# Set this value to 'agree' to accept our license:
# LICENSE: https://github.com/calendso/calendso/blob/main/LICENSE
# https://github.com/calendso/calendso/blob/main/LICENSE
#
# Summary of terms:
# - The codebase has to stay open source, whether it was modified or not
# - You can not repackage or sell the codebase
# - Acquire a commercial license to remove these terms by visiting: cal.com/sales
NEXT_PUBLIC_LICENSE_CONSENT=''
#
# To enable enterprise-only features, fill your license key in here.
# @see https://console.cal.com
CALCOM_LICENSE_KEY=
@@ -86,6 +85,9 @@ SENDGRID_EMAIL=
TWILIO_SID=
TWILIO_TOKEN=
TWILIO_MESSAGING_SID=
TWILIO_PHONE_NUMBER=
NEXT_PUBLIC_SENDER_ID=
TWILIO_VERIFY_SID=
# This is used so we can bypass emails in auth flows for E2E testing
# Set it to "1" if you need to run E2E tests locally
@@ -96,11 +98,12 @@ NEXT_PUBLIC_STRIPE_PRO_PLAN_PRICE=
NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE=
NEXT_PUBLIC_IS_PREMIUM_NEW_PLAN=0
NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
NEXT_PUBLIC_STRIPE_FREE_PLAN_PRICE=
STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRO_PLAN_PRODUCT_ID=
STRIPE_PREMIUM_PLAN_PRODUCT_ID=
STRIPE_FREE_PLAN_PRODUCT_ID=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
PAYMENT_FEE_FIXED=
PAYMENT_FEE_PERCENTAGE=
# Use for internal Public API Keys and optional
API_KEY_PREFIX=cal_
@@ -138,9 +141,11 @@ CLOSECOM_API_KEY=
# Sendgrid internal sync service
SENDGRID_SYNC_API_KEY=
# Sendgrid internal email sender
SENDGRID_API_KEY=
# Sentry
NEXT_PUBLIC_SENTRY_DSN=
SENTRY_IGNORE_API_RESOLUTION_ERROR=
# Change your Brand
NEXT_PUBLIC_APP_NAME="Cal.com"
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
NEXT_PUBLIC_COMPANY_NAME="Cal.com, Inc."
+7
View File
@@ -3,3 +3,10 @@
automerge:
- apps/web/public/static/locales/**/*.json
"❗️ migrations":
- packages/prisma/migrations/**/migration.sql
"❗️ .env changes":
- .env.example
- .env.appStore.example
-22
View File
@@ -1,22 +0,0 @@
name: Add bugs to Bug Hunting
on:
issues:
types:
- opened
- labeled
pull_request:
types:
- opened
- labeled
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.1.0
with:
project-url: https://github.com/orgs/calcom/projects/9
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: 🙋🏻‍♂️help wanted
+2 -1
View File
@@ -22,6 +22,7 @@ jobs:
upload_translations: true
download_translations: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# So required checks run on Crowdin PRs
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+37
View File
@@ -0,0 +1,37 @@
# .github/workflows/preview.yml
name: Preview Environment
env:
SNAPLET_ACCESS_TOKEN: ${{ secrets.SNAPLET_ACCESS_TOKEN }}
SNAPLET_PROJECT_ID: ${{ secrets.SNAPLET_PROJECT_ID }}
VERCEL_ACCESS_TOKEN: ${{ secrets.VERCEL_ACCESS_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
pull_request:
types: [opened, synchronize, closed]
branches:
- main
jobs:
db-preview-deploy:
if: ${{ github.event.action == 'opened' || github.event.action == 'synchronize' }}
runs-on: ubuntu-latest
steps:
- id: snaplet
uses: snaplet/action@v1
- uses: snaplet/vercel-action@v1
with:
env: |
DATABASE_URL=${{ steps.snaplet.outputs.database-url }}
db-preview-delete:
if: ${{ github.event.action == 'closed' }}
runs-on: ubuntu-latest
steps:
- uses: snaplet/action@v1
with:
delete: true
- uses: snaplet/vercel-action@v1
with:
delete: true
+2 -3
View File
@@ -12,8 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
# Checking the actor will prevent your Action run failing on non-Dependabot
# PRs but also ensures that it only does work for Dependabot PRs.
if: ${{ github.actor == 'github-actions[bot]' }}
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
sync-labels: ""
+17
View File
@@ -0,0 +1,17 @@
on:
workflow_dispatch:
push:
# Pattern matched against refs/tags
tags:
- "*" # Push events to every tag not containing /
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: main # Always checkout main even for tagged releases
fetch-depth: 0
token: ${{ secrets.GH_ACCESS_TOKEN }}
- run: git push origin +main:production
+3 -2
View File
@@ -1,7 +1,7 @@
name: Unit tests
on:
push:
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
branches: [ feat/routing-forms/reporting ] # TODO: Remove this after merged in main
pull_request_target: # So we can test on forks
branches:
- main
@@ -21,7 +21,8 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
fetch-depth: 2
- run: echo 'NODE_OPTIONS="--max_old_space_size=4096"' >> $GITHUB_ENV
# Should be an 8GB machine as per https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
- run: echo 'NODE_OPTIONS="--max_old_space_size=6144"' >> $GITHUB_ENV
- name: Use Node 16.x
uses: actions/setup-node@v3
with:
+1 -1
View File
@@ -1,4 +1,4 @@
{
"projectId": "cl4u26bwz7962859ply7ibuo43t",
"databaseUrl": "postgresql://postgres@localhost:5450/calendso"
"targetDatabaseUrl": "postgresql://postgres@localhost:5450/calendso"
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"version": "0.3.0"
"version": "0.22.3"
}
+2 -1
View File
@@ -6,5 +6,6 @@
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"spellright.language": ["en"],
"spellright.documentTypes": ["markdown", "typescript"]
"spellright.documentTypes": ["markdown", "typescript"],
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
}
+32 -65
View File
File diff suppressed because one or more lines are too long
+35
View File
@@ -0,0 +1,35 @@
/**
* This is a Playwright script Checkly generated for you based on your Vercel project.
* To learn more about Browser checks and Playwright visit: https://www.checklyhq.com/docs/browser-checks
*/
// Create a Chromium browser
const { chromium } = require("playwright");
// Checkly supports top level await, but we wrap your code in an async function so you can run it locally too.
async function run() {
const browser = await chromium.launch();
const page = await browser.newPage();
const targetUrl = process.env.ENVIRONMENT_URL || "https://cal.com";
await page.goto(`${targetUrl}/rick/test-location-link`);
await page.waitForSelector('[data-testid="day"][data-disabled="false"]');
await page.click('[data-testid="day"][data-disabled="false"]');
await page.waitForSelector('[data-testid="time"]');
await page.click('[data-testid="time"]');
await page.waitForSelector("#name");
await page.click("#name");
await page.type("#name", "Calcom");
await page.type('[name="email"]', "cal@cal.com");
await page.waitForSelector('[data-testid="confirm-book-button"]');
await page.click('[data-testid="confirm-book-button"]');
await browser.close();
}
run();
-5
View File
@@ -33,11 +33,6 @@
"generator": "secret",
"required": "true"
},
"NEXT_PUBLIC_LICENSE_CONSENT": {
"description": "License consent",
"value": "true",
"required": "true"
},
"CRON_API_KEY": {
"description": "ApiKey for cronjobs",
"value": ""
+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",
+1 -2
View File
@@ -14,14 +14,13 @@ Cal.com uses [TailwindCSS](https://tailwindcss.com) as a replacement for traditi
CSS files should be stored in the `styles` directory within the codebase.
Within the `styles` directory, you will find two CSS files, `fonts.css` and `global.css`. We suggest not to add to these files, and instead create new CSS files and import them into the application. This helps reduce conflicts when pulling in changes that we've made to either of the existing CSS files.
Within the `styles` directory, you will find a single CSS file, `global.css`. We suggest not to add to this file, and instead create new CSS files and import them into the application. This helps reduce conflicts when pulling in changes that we've made to either of the existing CSS files.
## Adding new stylesheets
Firstly, create the CSS file inside the `styles` directory.
Then, open the `pages/_app.tsx` file, and you will see the following two lines:
```javascript
import "../styles/fonts.css";
import "../styles/globals.css";
```
+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.
+16
View File
@@ -4,6 +4,7 @@ module.exports = {
stories: [
"../intro.stories.mdx",
"../../../packages/ui/components/**/*.stories.mdx",
"../../../packages/features/**/*.stories.mdx",
"../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
@@ -50,6 +51,21 @@ module.exports = {
vm: false,
zlib: false,
};
config.module.rules.push({
test: /\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true, // Enable modules to help you using className
},
},
],
include: path.resolve(__dirname, "../src"),
});
return config;
},
};
+5 -1
View File
@@ -15,7 +15,11 @@ export const parameters = {
},
};
addDecorator((storyFn) => <I18nextProvider i18n={i18n}>{storyFn()}</I18nextProvider>);
addDecorator((storyFn) => (
<I18nextProvider i18n={i18n}>
<div style={{ margin: "2rem" }}>{storyFn()}</div>
</I18nextProvider>
));
window.getEmbedNamespace = () => {
const url = new URL(document.URL);
+4 -3
View File
@@ -3,11 +3,12 @@ import { classNames } from "@calcom/lib";
interface ExampleProps {
children: React.ReactNode;
title: string;
isFullWidth?: boolean;
}
export const Example = ({ children, title }: ExampleProps) => {
export const Example = ({ children, title, isFullWidth = false }: ExampleProps) => {
return (
<div className="examples-item">
<span className="examples-item-title">{title}</span>
<div className={classNames("examples-item", isFullWidth && "w-full")}>
<span className={classNames("examples-item-title", isFullWidth && "mb-0 mt-2")}>{title}</span>
<div className="examples-item-content">{children}</div>
</div>
);
+1 -1
View File
@@ -19,7 +19,7 @@ export function VariantsTable({
<div
className={classNames(
isDark &&
"relative py-8 before:absolute before:left-0 before:top-0 before:block before:h-full before:w-screen before:bg-[#22252A]"
"relative py-8 before:absolute before:left-0 before:top-0 before:block before:h-full before:w-screen before:bg-[#1C1C1C]"
)}>
<div className="z-1 relative mx-auto w-full max-w-[1200px] overflow-auto pr-8 pt-6">
<table>
+2 -2
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"
},
@@ -49,6 +49,6 @@
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.2.1",
"typescript": "^4.7.4",
"vite": "^3.1.0"
"vite": "^2.9.15"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
@tailwind components;
@tailwind utilities;
@import "../../../packages/ui/styles/shared-globals.css"
@import "../../../packages/ui/styles/shared-globals.css"
+7 -1
View File
@@ -1,3 +1,5 @@
@import url("../../../packages/features/calendars/weeklyview/styles/styles.css");
.sbdocs {
font-family: 'Inter var' !important;
padding: 0!important;
@@ -36,7 +38,7 @@
/** Docs table **/
.custom-args-wrapper{
max-height: 600px;
max-height: 400px;
overflow-y: scroll;
overflow-x: hidden;
margin-bottom: 1rem;
@@ -187,3 +189,7 @@
padding: 24px !important;
}
}
.docs-story {
padding: 24px !important;
}
+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
-15
View File
@@ -1,15 +0,0 @@
declare module "@wojtekmaj/react-daterange-picker/dist/entry.nostyle" {
import { CalendarProps } from "react-calendar";
export type DateRangePickerCalendarProps = Omit<
CalendarProps,
"calendarClassName" | "onChange" | "value"
> & {
calendarClassName?: string;
onChange: (value: [Date, Date]) => void;
value: [Date, Date];
clearIcon: JSX.Element | null;
calendarIcon: JSX.Element | null;
rangeDivider: JSX.Element | null;
};
export default function DateRangePicker(props: DateRangePickerCalendarProps): JSX.Element;
}
-119
View File
@@ -598,60 +598,6 @@ paths:
summary: Gets and stores the OAuth token
tags:
- Integrations
/api/user/profile:
patch:
description: Updates a user's profile.
summary: Updates a user's profile
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
description:
type: string
avatar:
type: string
timeZone:
type: string
weekStart:
type: string
hideBranding:
type: string
theme:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
message:
type: string
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
properties:
message:
type: string
"500":
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
/api/me:
get:
description: Gets current user's profile.
@@ -686,71 +632,6 @@ paths:
properties:
message:
type: string
/api/user/membership:
get:
description: Get a list of the teams the user has joined.
summary: Get the teams a user is joined to
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
message:
type: string
"401":
description: Unauthorized
content:
application/json:
schema:
type: object
properties:
message:
type: string
"500":
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
patch:
description: Accept team invitation
summary: Accept team invitation.
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
properties:
teamId:
type: string
delete:
description: Leave team or decline membership invite of current user
summary: Leave team or decline team invite.
tags:
- User
requestBody:
content:
application/json:
schema:
type: object
properties:
teamId:
type: string
"/api/{team}":
delete:
description: Deletes a team
+1 -1
View File
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Icon } from "@calcom/ui";
export default function AddToHomescreen() {
const { t } = useLocale();
@@ -36,7 +36,7 @@ const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App
const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorProps): JSX.Element | null => {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations", { variant: "calendar", onlyInstalled: true }]);
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
return (
<QueryCell
-306
View File
@@ -1,306 +0,0 @@
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { InstallAppButton } from "@calcom/app-store/components";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { App as AppType } from "@calcom/types/App";
import { Button, SkeletonButton } from "@calcom/ui";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import { Badge } from "@calcom/ui/components/badge";
import showToast from "@calcom/ui/v2/core/notifications";
import DisconnectIntegration from "@components/integrations/DisconnectIntegration";
const Component = ({
name,
type,
logo,
body,
categories,
author,
price = 0,
commission,
isGlobal = false,
feeType,
docs,
website,
email,
tos,
privacy,
isProOnly,
}: Parameters<typeof App>[0]) => {
const { t } = useLocale();
const router = useRouter();
const { data: user } = trpc.useQuery(["viewer.me"]);
const utils = trpc.useContext();
const handleOpenChange = () => {
utils.invalidateQueries(["viewer.integrations"]);
router.replace("/apps/installed");
};
const mutation = useAddAppMutation(null, {
onSuccess: () => {
showToast("App successfully installed", "success");
},
onError: (error) => {
if (error instanceof Error) showToast(error.message || "App could not be installed", "error");
},
});
const priceInDollar = Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price);
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
const appCredentials = trpc.useQuery(["viewer.appCredentialsByType", { appType: type }], {
onSuccess(data) {
setExistingCredentials(data);
},
});
const allowedMultipleInstalls = categories.indexOf("calendar") > -1;
return (
<div className="-mx-4 md:-mx-8">
<div className="px-8">
<Link href="/apps">
<a className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800">
<Icon.FiChevronLeft className="h-5 w-5" /> {t("browse_apps")}
</a>
</Link>
<div className="items-center justify-between py-4 sm:flex sm:py-8">
<div className="flex">
<img className="h-16 w-16 rounded-sm" src={logo} alt={name} />
<header className="px-4 py-2">
<div className="flex items-center">
<h1 className="font-cal text-xl text-gray-900">{name}</h1>
</div>
<h2 className="text-sm text-gray-500">
<span className="capitalize">{categories[0]}</span> • {t("published_by", { author })}
</h2>
</header>
</div>
<div className="mt-4 sm:mt-0 sm:text-right">
{!appCredentials.isLoading ? (
isGlobal ||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
<div className="flex space-x-3">
<Button StartIcon={Icon.FiCheck} color="secondary" disabled>
{existingCredentials.length > 0
? t("active_install", { count: existingCredentials.length })
: t("globally_install")}
</Button>
{!isGlobal && (
<InstallAppButton
type={type}
isProOnly={isProOnly}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
onClick: () => {
mutation.mutate({ type });
},
loading: mutation.isLoading,
};
}
return (
<Button StartIcon={Icon.FiPlus} {...props} data-testid="install-app-button">
{t("add_another")}
</Button>
);
}}
/>
)}
</div>
) : existingCredentials.length > 0 ? (
<DisconnectIntegration
id={existingCredentials[0]}
render={(btnProps) => (
<Button {...btnProps} color="warn" data-testid={type + "-integration-disconnect-button"}>
{t("disconnect")}
</Button>
)}
onOpenChange={handleOpenChange}
/>
) : (
<InstallAppButton
type={type}
isProOnly={isProOnly}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
onClick: () => {
mutation.mutate({ type });
},
loading: mutation.isLoading,
};
}
return (
<Button data-testid="install-app-button" {...props}>
{t("install_app")}
</Button>
);
}}
/>
))
) : (
<SkeletonButton width="24" height="10" />
)}
{price !== 0 && (
<small className="block text-right">
{feeType === "usage-based" ? commission + "% + " + priceInDollar + "/booking" : priceInDollar}
{feeType === "monthly" && "/" + t("month")}
</small>
)}
</div>
</div>
</div>
<div className="justify-between px-8 py-10 md:flex">
<div className="prose-sm prose mb-6">{body}</div>
<div className="md:max-w-80 flex-1 md:ml-8">
<h4 className="font-medium text-gray-900 ">{t("categories")}</h4>
<div className="space-x-2">
{categories.map((category) => (
<Link href={"/apps/categories/" + category} key={category}>
<a>
<Badge variant="success">{category}</Badge>
</a>
</Link>
))}
</div>
<h4 className="mt-8 font-medium text-gray-900 ">{t("pricing")}</h4>
<small>
{price === 0 ? (
"Free"
) : (
<>
{Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
useGrouping: false,
}).format(price)}
{feeType === "monthly" && "/" + t("month")}
</>
)}
</small>
<h4 className="mt-8 mb-2 font-medium text-gray-900 ">{t("learn_more")}</h4>
<ul className="prose -ml-1 -mr-1 text-xs leading-5">
{docs && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={docs}>
<Icon.FiBookOpen className="mr-1 -mt-1 inline h-4 w-4" />
{t("documentation")}
</a>
</li>
)}
{website && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={website}>
<Icon.FiExternalLink className="mr-1 -mt-px inline h-4 w-4" />
{website.replace("https://", "")}
</a>
</li>
)}
{email && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={"mailto:" + email}>
<Icon.FiMail className="mr-1 -mt-px inline h-4 w-4" />
{email}
</a>
</li>
)}
{tos && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={tos}>
<Icon.FiFile className="mr-1 -mt-px inline h-4 w-4" />
{t("terms_of_service")}
</a>
</li>
)}
{privacy && (
<li>
<a
target="_blank"
rel="noreferrer"
className="text-blue-500 no-underline hover:underline"
href={privacy}>
<Icon.FiShield className="mr-1 -mt-px inline h-4 w-4" />
{t("privacy_policy")}
</a>
</li>
)}
</ul>
<hr className="my-6" />
<small className="leading-1 block text-gray-500">
Every app published on the Cal.com App Store is open source and thoroughly tested via peer
reviews. Nevertheless, Cal.com, Inc. does not endorse or certify these apps unless they are
published by Cal.com. If you encounter inappropriate content or behaviour please report it.
</small>
<a className="mt-2 block text-xs text-red-500" href="mailto:help@cal.com">
<Icon.FiFlag className="inline h-3 w-3" /> Report App
</a>
</div>
</div>
</div>
);
};
export default function App(props: {
name: string;
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
body: React.ReactNode;
categories: string[];
author: string;
pro?: boolean;
price?: number;
commission?: number;
feeType?: AppType["feeType"];
docs?: string;
website?: string;
email: string; // required
tos?: string;
privacy?: string;
licenseRequired: AppType["licenseRequired"];
isProOnly: AppType["isProOnly"];
}) {
return (
<Shell large isPublic>
{props.licenseRequired ? (
<LicenseRequired>
<Component {...props} />
</LicenseRequired>
) : (
<Component {...props} />
)}
</Shell>
);
}
-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}</>;
};
@@ -23,7 +23,7 @@ const DestinationCalendarSelector = ({
destinationCalendar,
}: Props): JSX.Element | null => {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.connectedCalendars"]);
const query = trpc.viewer.connectedCalendars.useQuery();
const [selectedOption, setSelectedOption] = useState<{ value: string; label: string } | null>(null);
// Extra styles to show prefixed text in react-select
+257 -224
View File
@@ -4,14 +4,22 @@ import { NextRouter, useRouter } from "next/router";
import { createRef, forwardRef, MutableRefObject, RefObject, useRef, useState } from "react";
import { components, ControlProps } from "react-select";
import { APP_NAME, EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { HorizontalTabs, showToast, Switch } from "@calcom/ui/v2";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/v2/core/Dialog";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
import {
Button,
Dialog,
DialogClose,
DialogContent,
HorizontalTabs,
Icon,
InputLeading,
Label,
showToast,
Switch,
TextArea,
TextField,
} from "@calcom/ui";
import ColorPicker from "@components/ui/colorpicker";
import Select from "@components/ui/form/Select";
@@ -36,6 +44,7 @@ type PreviewState = {
palette: {
brandColor: string;
};
hideEventTypeDetails: boolean;
};
const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl"];
@@ -57,10 +66,13 @@ const goto = (router: NextRouter, searchParams: Record<string, string>) => {
};
const removeQueryParams = (router: NextRouter, queryParams: string[]) => {
const params = new URLSearchParams(window.location.search);
queryParams.forEach((param) => {
delete router.query[param];
params.delete(param);
});
router.push(`${router.asPath.split("?")[0]}?${router.query.toString()}`);
router.push(`${router.asPath.split("?")[0]}?${params.toString()}`);
};
/**
@@ -114,10 +126,12 @@ const getEmbedUIInstructionString = ({
apiName,
theme,
brandColor,
hideEventTypeDetails,
}: {
apiName: string;
theme?: string;
brandColor: string;
hideEventTypeDetails: boolean;
}) => {
theme = theme !== "auto" ? theme : undefined;
return getInstructionString({
@@ -130,6 +144,7 @@ const getEmbedUIInstructionString = ({
brandColor,
},
},
hideEventTypeDetails: hideEventTypeDetails,
},
});
};
@@ -242,18 +257,21 @@ const getEmbedTypeSpecificString = ({
apiName: string;
theme: PreviewState["theme"];
brandColor: string;
hideEventTypeDetails: boolean;
};
if (embedFramework === "react") {
uiInstructionStringArg = {
apiName: "cal",
theme: previewState.theme,
brandColor: previewState.palette.brandColor,
hideEventTypeDetails: previewState.hideEventTypeDetails,
};
} else {
uiInstructionStringArg = {
apiName: "Cal",
theme: previewState.theme,
brandColor: previewState.palette.brandColor,
hideEventTypeDetails: previewState.hideEventTypeDetails,
};
}
if (!frameworkCodes[embedType]) {
@@ -486,7 +504,9 @@ const tabs = [
return (
<>
<div>
<small className="flex py-4 text-neutral-500">{t("place_where_cal_widget_appear")}</small>
<small className="flex py-4 text-neutral-500">
{t("place_where_cal_widget_appear", { appName: APP_NAME })}
</small>
</div>
<TextArea
data-testid="embed-code"
@@ -607,19 +627,19 @@ const ChooseEmbedTypesDialogContent = () => {
const { t } = useLocale();
const router = useRouter();
return (
<DialogContent type="creation" useOwnActionButtons size="lg">
<DialogContent type="creation" size="lg">
<div className="mb-4">
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
{t("how_you_want_add_cal_site")}
{t("how_you_want_add_cal_site", { appName: APP_NAME })}
</h3>
<div>
<p className="text-sm text-gray-500">{t("choose_ways_put_cal_site")}</p>
<p className="text-sm text-gray-500">{t("choose_ways_put_cal_site", { appName: APP_NAME })}</p>
</div>
</div>
<div className="flex items-start">
{embeds.map((embed, index) => (
<button
className="mr-2 w-1/3 border border-transparent p-3 text-left hover:rounded-md hover:border-gray-200 hover:bg-neutral-100"
className="w-1/3 border border-transparent p-3 text-left hover:rounded-md hover:border-gray-200 hover:bg-neutral-100 ltr:mr-2 rtl:ml-2"
key={index}
data-testid={embed.type}
onClick={() => {
@@ -676,6 +696,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
theme: Theme.auto,
floatingPopup: {},
elementClick: {},
hideEventTypeDetails: false,
palette: {
brandColor: "#000000",
},
@@ -740,6 +761,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
name: "ui",
arg: {
theme: previewState.theme,
hideEventTypeDetails: previewState.hideEventTypeDetails,
styles: {
branding: {
...previewState.palette,
@@ -785,7 +807,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
];
return (
<DialogContent size="xl" className="p-0.5" type="creation" useOwnActionButtons>
<DialogContent size="xl" className="p-0.5" type="creation">
<div className="flex">
<div className="flex w-1/3 flex-col bg-gray-50 p-8">
<h3 className="mb-2 flex text-xl font-bold leading-6 text-gray-900" id="modal-title">
@@ -798,245 +820,258 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
{embed.title}
</h3>
<hr className={classNames("mt-4", embedType === "element-click" ? "hidden" : "")} />
<div className={classNames("mt-4 font-medium", embedType === "element-click" ? "hidden" : "")}>
<Collapsible
open={isEmbedCustomizationOpen}
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
<CollapsibleTrigger
type="button"
className="flex w-full items-center text-base font-medium text-neutral-900">
<div>
{embedType === "inline"
? "Inline Embed Customization"
: embedType === "floating-popup"
? "Floating Popup Customization"
: "Element Click Customization"}
</div>
<Icon.FiChevronRight
className={`${
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
/>
</CollapsibleTrigger>
<CollapsibleContent className="text-sm">
<div className={classNames("mt-6", embedType === "inline" ? "block" : "hidden")}>
{/*TODO: Add Auto/Fixed toggle from Figma */}
<div className="text-sm">Embed Window Sizing</div>
<div className="justify-left flex items-center">
<TextField
name="width"
labelProps={{ className: "hidden" }}
required
value={previewState.inline.width}
onChange={(e) => {
setPreviewState((previewState) => {
const width = e.target.value || "100%";
return {
...previewState,
inline: {
...previewState.inline,
width,
},
};
});
}}
addOnLeading={<InputLeading>W</InputLeading>}
/>
<span className="p-2">×</span>
<TextField
labelProps={{ className: "hidden" }}
name="height"
value={previewState.inline.height}
required
onChange={(e) => {
const height = e.target.value || "100%";
setPreviewState((previewState) => {
return {
...previewState,
inline: {
...previewState.inline,
height,
},
};
});
}}
addOnLeading={<InputLeading>H</InputLeading>}
/>
<div className="flex flex-col overflow-y-auto">
<div className={classNames("mt-4 font-medium", embedType === "element-click" ? "hidden" : "")}>
<Collapsible
open={isEmbedCustomizationOpen}
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
<CollapsibleTrigger
type="button"
className="flex w-full items-center text-base font-medium text-neutral-900">
<div>
{embedType === "inline"
? "Inline Embed Customization"
: embedType === "floating-popup"
? "Floating Popup Customization"
: "Element Click Customization"}
</div>
</div>
<div
className={classNames(
"mt-4 items-center justify-between",
embedType === "floating-popup" ? "" : "hidden"
)}>
<div className="mb-2 text-sm">Button Text</div>
{/* Default Values should come from preview iframe */}
<TextField
name="buttonText"
labelProps={{ className: "hidden" }}
onChange={(e) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonText: e.target.value,
},
};
});
}}
defaultValue="Book my Cal"
required
<Icon.FiChevronRight
className={`${
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
/>
</div>
<div
className={classNames(
"mt-4 flex items-center justify-start",
embedType === "floating-popup" ? "space-x-2" : "hidden"
)}>
<Switch
defaultChecked={true}
onCheckedChange={(checked) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
hideButtonIcon: !checked,
},
};
});
}}
/>
<div className="text-sm">Display Calendar Icon Button</div>
</div>
<div
className={classNames(
"mt-4 items-center justify-between",
embedType === "floating-popup" ? "" : "hidden"
)}>
<div className="mb-2">Position of Button</div>
<Select
onChange={(position) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonPosition: position?.value,
},
};
});
}}
defaultValue={FloatingPopupPositionOptions[0]}
options={FloatingPopupPositionOptions}
/>
</div>
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div>Button Color</div>
<div className="w-full">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
</CollapsibleTrigger>
<CollapsibleContent className="text-sm">
<div className={classNames("mt-6", embedType === "inline" ? "block" : "hidden")}>
{/*TODO: Add Auto/Fixed toggle from Figma */}
<div className="text-sm">Embed Window Sizing</div>
<div className="justify-left flex items-center">
<TextField
labelProps={{ className: "hidden" }}
required
value={previewState.inline.width}
onChange={(e) => {
setPreviewState((previewState) => {
const width = e.target.value || "100%";
return {
...previewState,
inline: {
...previewState.inline,
width,
},
};
});
}}
addOnLeading={<InputLeading>W</InputLeading>}
/>
<span className="p-2">×</span>
<TextField
labelProps={{ className: "hidden" }}
value={previewState.inline.height}
required
onChange={(e) => {
const height = e.target.value || "100%";
setPreviewState((previewState) => {
return {
...previewState,
inline: {
...previewState.inline,
height,
},
};
});
}}
addOnLeading={<InputLeading>H</InputLeading>}
/>
</div>
</div>
<div
className={classNames(
"mt-4 items-center justify-between",
embedType === "floating-popup" ? "" : "hidden"
)}>
<div className="mb-2 text-sm">Button Text</div>
{/* Default Values should come from preview iframe */}
<TextField
labelProps={{ className: "hidden" }}
onChange={(e) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonColor: color,
buttonText: e.target.value,
},
};
});
}}
defaultValue="Book my Cal"
required
/>
</div>
</div>
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div>Text Color</div>
<div className="w-full">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
<div
className={classNames(
"mt-4 flex items-center justify-start",
embedType === "floating-popup" ? "space-x-2 rtl:space-x-reverse" : "hidden"
)}>
<Switch
defaultChecked={true}
onCheckedChange={(checked) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonTextColor: color,
hideButtonIcon: !checked,
},
};
});
}}
/>
<div className="text-sm">Display Calendar Icon Button</div>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</div>
<hr className="mt-4" />
<div className="mt-4 font-medium">
<Collapsible
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>
<Icon.FiChevronRight
className={`${
isBookingCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
/>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="mt-6 text-sm">
<Label className="">
<div className="mb-2">Theme</div>
<div
className={classNames(
"mt-4 items-center justify-between",
embedType === "floating-popup" ? "" : "hidden"
)}>
<div className="mb-2">Position of Button</div>
<Select
className="w-full"
defaultValue={ThemeOptions[0]}
components={{
Control: ThemeSelectControl,
}}
onChange={(option) => {
if (!option) {
return;
}
onChange={(position) => {
setPreviewState((previewState) => {
return {
...previewState,
theme: option.value,
floatingPopup: {
...previewState.floatingPopup,
buttonPosition: position?.value,
},
};
});
}}
options={ThemeOptions}
defaultValue={FloatingPopupPositionOptions[0]}
options={FloatingPopupPositionOptions}
/>
</Label>
{[
{ name: "brandColor", title: "Brand Color" },
// { name: "lightColor", title: "Light Color" },
// { name: "lighterColor", title: "Lighter Color" },
// { name: "lightestColor", title: "Lightest Color" },
// { name: "highlightColor", title: "Highlight Color" },
// { name: "medianColor", title: "Median Color" },
].map((palette) => (
<Label key={palette.name} className="pb-4">
<div className="mb-2 pt-2">{palette.title}</div>
<div className="w-full">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
addToPalette({
[palette.name as keyof typeof previewState["palette"]]: color,
});
}}
/>
</div>
</div>
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div>Button Color</div>
<div className="w-full">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonColor: color,
},
};
});
}}
/>
</div>
</div>
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
<div>Text Color</div>
<div className="w-full">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonTextColor: color,
},
};
});
}}
/>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</div>
<hr className="mt-4" />
<div className="mt-4 font-medium">
<Collapsible
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>
<Icon.FiChevronRight
className={`${
isBookingCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
/>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="mt-6 text-sm">
<div className="mb-4 flex items-center justify-start space-x-2 rtl:space-x-reverse">
<Switch
checked={previewState.hideEventTypeDetails}
onCheckedChange={(checked) => {
setPreviewState((previewState) => {
return {
...previewState,
hideEventTypeDetails: checked,
};
});
}}
/>
<div className="text-sm">{t("hide_eventtype_details")}</div>
</div>
<Label className="">
<div className="mb-2">Theme</div>
<Select
className="w-full"
defaultValue={ThemeOptions[0]}
components={{
Control: ThemeSelectControl,
}}
onChange={(option) => {
if (!option) {
return;
}
setPreviewState((previewState) => {
return {
...previewState,
theme: option.value,
};
});
}}
options={ThemeOptions}
/>
</Label>
))}
</div>
</CollapsibleContent>
</Collapsible>
{[
{ name: "brandColor", title: "Brand Color" },
// { name: "lightColor", title: "Light Color" },
// { name: "lighterColor", title: "Lighter Color" },
// { name: "lightestColor", title: "Lightest Color" },
// { name: "highlightColor", title: "Highlight Color" },
// { name: "medianColor", title: "Median Color" },
].map((palette) => (
<Label key={palette.name} className="pb-4">
<div className="mb-2 pt-2">{palette.title}</div>
<div className="w-full">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
addToPalette({
[palette.name as keyof typeof previewState["palette"]]: color,
});
}}
/>
</div>
</Label>
))}
</div>
</CollapsibleContent>
</Collapsible>
</div>
</div>
</div>
<div className="flex w-2/3 flex-col p-8">
@@ -1081,9 +1116,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
{t("copy_code")}
</Button>
) : null}
<DialogClose asChild>
<Button color="secondary">{t("Close")}</Button>
</DialogClose>
<DialogClose />
</div>
</div>
);
+7 -2
View File
@@ -1,10 +1,11 @@
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
import { getDirFromLang } from "@calcom/lib/i18n";
import { trpc } from "@calcom/trpc/react";
export function useViewerI18n() {
return trpc.useQuery(["viewer.public.i18n"], {
return trpc.viewer.public.i18n.useQuery(undefined, {
staleTime: Infinity,
/**
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
@@ -21,12 +22,16 @@ export function useViewerI18n() {
*/
const I18nLanguageHandler = (): null => {
const { i18n } = useTranslation("common");
const locale = useViewerI18n().data?.locale;
const locale = useViewerI18n().data?.locale || "en";
useEffect(() => {
if (locale && i18n.language && i18n.language !== locale) {
if (typeof i18n.changeLanguage === "function") i18n.changeLanguage(locale);
}
const dir = getDirFromLang(locale);
document.documentElement.setAttribute("lang", locale);
document.documentElement.setAttribute("dir", dir);
}, [locale, i18n]);
return null;
+3 -8
View File
@@ -1,8 +1,7 @@
import { FormEvent, useCallback, useEffect, useState } from "react";
import Cropper from "react-easy-crop";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui";
import { Area, getCroppedImg } from "@lib/cropImage";
import { useFileReader } from "@lib/hooks/useFileReader";
@@ -157,12 +156,8 @@ export default function ImageUploader({
</div>
</div>
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose asChild>
<Button onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</Button>
</DialogClose>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<DialogClose onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</DialogClose>
<DialogClose color="secondary">{t("cancel")}</DialogClose>
</div>
</DialogContent>
</Dialog>
+1 -1
View File
@@ -1,4 +1,4 @@
/**
* @deprecated Use custom Skeletons instead
**/
export { default } from "@calcom/ui/Loader";
export { Loader as default } from "@calcom/ui";
+1 -1
View File
@@ -62,7 +62,7 @@ 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(
+1 -3
View File
@@ -1,8 +1,6 @@
import React, { ComponentProps } from "react";
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import { ErrorBoundary, Icon, Shell } from "@calcom/ui";
import NavTabs from "./NavTabs";
@@ -1,13 +1,10 @@
import React from "react";
import { OptionProps } from "react-select";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui";
import { Icon } from "@calcom/ui/Icon";
import { Select } from "@calcom/ui/v2";
import { Button, Icon, Select } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
@@ -42,7 +39,7 @@ const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App
const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorProps): JSX.Element | null => {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations", { variant: "calendar", onlyInstalled: true }]);
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
return (
<QueryCell
@@ -64,12 +61,10 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
<Select
name="additionalCalendar"
placeholder={
<div className="flex justify-start text-black">
<Icon.FiPlus className="-ml-1 h-4 w-4 ltr:mr-2 rtl:ml-2 rtl:-mr-1" />
<p>{t("add")}</p>
</div>
<Button StartIcon={Icon.FiPlus} color="secondary">
{t("add")}
</Button>
}
className="min-w-44"
options={options}
isSearchable={false}
isLoading={isLoading}
@@ -83,11 +78,16 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
width: "max-content",
right: "0",
}),
indicatorSeparator: (defaultStyles) => ({
...defaultStyles,
display: "none",
}),
control: (defaultStyles) => ({
...defaultStyles,
padding: "0",
border: "0",
borderRadius: "inherit",
borderRadius: "6px",
minHeight: "auto",
}),
dropdownIndicator: (defaultStyles) => ({
...defaultStyles,
+28 -19
View File
@@ -4,18 +4,14 @@ import React, { useState } from "react";
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 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 { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { showToast, SkeletonText } from "@calcom/ui/v2";
import { SkeletonButton, Shell } from "@calcom/ui/v2";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import HeadSeo from "@components/seo/head-seo";
import { Button, Icon, Shell, showToast, SkeletonButton, SkeletonText, HeadSeo } from "@calcom/ui";
const Component = ({
name,
@@ -43,7 +39,8 @@ const Component = ({
const router = useRouter();
const mutation = useAddAppMutation(null, {
onSuccess: () => {
onSuccess: (data) => {
if (data?.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
@@ -58,13 +55,18 @@ const Component = ({
}).format(price);
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
const appCredentials = trpc.useQuery(["viewer.appCredentialsByType", { appType: type }], {
onSuccess(data) {
setExistingCredentials(data);
},
});
const appCredentials = trpc.viewer.appCredentialsByType.useQuery(
{ appType: type },
{
onSuccess(data) {
setExistingCredentials(data);
},
}
);
const allowedMultipleInstalls = categories.indexOf("calendar") > -1;
// variant not other allows, an app to be shown in calendar category without requiring an actual calendar connection e.g. vimcal
// Such apps, can only be installed once.
const allowedMultipleInstalls = categories.indexOf("calendar") > -1 && variant !== "other";
return (
<div className="relative flex-1 flex-col items-start justify-start px-4 md:flex md:px-8 lg:flex-row lg:px-0">
@@ -96,8 +98,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>
@@ -119,6 +123,7 @@ const Component = ({
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
onClick: () => {
mutation.mutate({ type, variant, slug });
},
@@ -143,6 +148,7 @@ const Component = ({
</div>
) : existingCredentials.length > 0 ? (
<DisconnectIntegration
buttonProps={{ color: "secondary" }}
label={t("disconnect")}
credentialId={existingCredentials[0]}
onSuccess={() => {
@@ -156,6 +162,7 @@ const Component = ({
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
onClick: () => {
mutation.mutate({ type, variant, slug });
},
@@ -190,7 +197,7 @@ const Component = ({
<h4 className="mt-8 font-semibold text-gray-900 ">{t("pricing")}</h4>
<span>
{price === 0 ? (
"Free"
t("free_to_use_apps")
) : (
<>
{Intl.NumberFormat("en-US", {
@@ -268,8 +275,10 @@ const Component = ({
)}
</ul>
<hr className="my-8" />
<span className="leading-1 block text-xs text-gray-500">{t("every_app_published")}</span>
<a className="mt-2 block text-xs text-red-500" href="mailto:help@cal.com">
<span className="leading-1 block text-xs text-gray-500">
{t("every_app_published", { appName: APP_NAME, companyName: COMPANY_NAME })}
</span>
<a className="mt-2 block text-xs text-red-500" href={`mailto:${SUPPORT_MAIL_ADDRESS}`}>
<Icon.FiFlag className="inline h-3 w-3" /> {t("report_app")}
</a>
</div>
@@ -3,18 +3,21 @@ import Link from "next/link";
import { Fragment } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
import { Button } from "@calcom/ui/components";
import { Alert, EmptyScreen } from "@calcom/ui/v2";
import { List } from "@calcom/ui/v2/core/List";
import { ShellSubHeading } from "@calcom/ui/v2/core/Shell";
import Switch from "@calcom/ui/v2/core/Switch";
import showToast from "@calcom/ui/v2/core/notifications";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import {
Alert,
Button,
EmptyScreen,
Icon,
List,
ShellSubHeading,
showToast,
AppSkeletonLoader as SkeletonLoader,
Switch,
} from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
@@ -77,7 +80,7 @@ function CalendarSwitch(props: {
},
{
async onSettled() {
await utils.invalidateQueries(["viewer.integrations"]);
await utils.viewer.integrations.invalidate();
},
onError() {
showToast(`Something went wrong when toggling "${props.title}""`, "error");
@@ -107,7 +110,7 @@ function CalendarSwitch(props: {
function CalendarList(props: Props) {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations", { variant: "calendar", onlyInstalled: false }]);
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
return (
<QueryCell
@@ -144,7 +147,9 @@ function CalendarList(props: Props) {
// todo: @hariom extract this into packages/apps-store as "GeneralAppSettings"
function ConnectedCalendarsList(props: Props) {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.connectedCalendars"], { suspense: true });
const query = trpc.viewer.connectedCalendars.useQuery(undefined, {
suspense: true,
});
const { fromOnboarding } = props;
return (
<QueryCell
@@ -154,6 +159,7 @@ function ConnectedCalendarsList(props: Props) {
if (!data.connectedCalendars.length) {
return null;
}
return (
<List className="flex flex-col gap-6" noBorderTreatment>
{data.connectedCalendars.map((item) => (
@@ -163,7 +169,7 @@ function ConnectedCalendarsList(props: Props) {
slug={item.integration.slug}
title={item.integration.title}
logo={item.integration.logo}
description={item.primary?.externalId || "No external Id"}
description={item.primary?.email ?? item.integration.description}
separate={true}
actions={
<div className="flex w-32 justify-end">
@@ -235,19 +241,19 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
const utils = trpc.useContext();
const onChanged = () =>
Promise.allSettled([
utils.invalidateQueries(["viewer.integrations", { variant: "calendar", onlyInstalled: true }], {
exact: true,
}),
utils.invalidateQueries(["viewer.connectedCalendars"]),
utils.viewer.integrations.invalidate(
{ variant: "calendar", onlyInstalled: true },
{
exact: true,
}
),
utils.viewer.connectedCalendars.invalidate(),
]);
const query = trpc.useQuery(["viewer.connectedCalendars"]);
const installedCalendars = trpc.useQuery([
"viewer.integrations",
{ variant: "calendar", onlyInstalled: true },
]);
const mutation = trpc.useMutation("viewer.setDestinationCalendar", {
const query = trpc.viewer.connectedCalendars.useQuery();
const installedCalendars = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
const mutation = trpc.viewer.setDestinationCalendar.useMutation({
onSuccess: () => {
utils.invalidateQueries(["viewer.connectedCalendars"]);
utils.viewer.connectedCalendars.invalidate();
},
});
return (
@@ -324,7 +330,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>
}
@@ -23,7 +23,7 @@ const DestinationCalendarSelector = ({
destinationCalendar,
}: Props): JSX.Element | null => {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.connectedCalendars"]);
const query = trpc.viewer.connectedCalendars.useQuery();
const [selectedOption, setSelectedOption] = useState<{ value: string; label: string } | null>(null);
// Extra styles to show prefixed text in react-select
@@ -3,8 +3,7 @@ import { useRouter } from "next/router";
import { ReactNode, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/core/List";
import { Icon, ListItem, ListItemText, ListItemTitle, showToast } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -27,6 +26,13 @@ function IntegrationListItem(props: {
const [highlight, setHighlight] = useState(hl === props.slug);
const title = props.name || props.title;
// The highlight is to show a newly installed app, coming from the app's
// redirection after installation, so we proceed to show the corresponding
// message
if (highlight) {
showToast(t("app_successfully_installed"), "success");
}
useEffect(() => {
const timer = setTimeout(() => setHighlight(false), 3000);
return () => {
@@ -38,7 +44,7 @@ function IntegrationListItem(props: {
expanded={!!props.children}
className={classNames(
props.separate ? "rounded-md" : "first:rounded-t-md last:rounded-b-md",
"my-0 flex-col border transition-colors duration-500 ",
"my-0 flex-col border transition-colors duration-500",
highlight ? "bg-yellow-100" : ""
)}>
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-4 rtl:space-x-reverse")}>
@@ -50,7 +56,7 @@ function IntegrationListItem(props: {
<ListItemText component="p">{props.description}</ListItemText>
{/* Alert error that key stopped working. */}
{props.invalidCredential && (
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-2 rtl:space-x-reverse">
<Icon.FiAlertCircle className="w-8 text-red-500 sm:w-4" />
<ListItemText component="p" className="whitespace-pre-wrap text-red-500">
{t("invalid_credential")}
@@ -0,0 +1,55 @@
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import React, { ComponentProps } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen, Icon, Shell, HorizontalTabs, HorizontalTabItemProps } from "@calcom/ui";
const tabs: HorizontalTabItemProps[] = [
{
name: "app_store",
href: "/apps",
},
{
name: "installed_apps",
href: "/apps/installed",
},
];
type AppsLayoutProps = {
children: React.ReactNode;
actions?: (className?: string) => JSX.Element;
emptyStore?: boolean;
} & Omit<ComponentProps<typeof Shell>, "actions">;
export default function AppsLayout({ children, actions, emptyStore, ...rest }: AppsLayoutProps) {
const { t } = useLocale();
const session = useSession();
const router = useRouter();
if (session.status === "loading") return <></>;
return (
<Shell {...rest} actions={actions?.("hidden sm: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
Icon={Icon.FiAlertCircle}
headline={t("no_apps")}
description={session.data?.user.role === "ADMIN" ? "You can enable apps in the settings" : ""}
buttonText={session.data?.user.role === "ADMIN" ? t("apps_settings") : ""}
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
/>
) : (
<>{children}</>
)}
</main>
</div>
</Shell>
);
}
export const getLayout = (page: React.ReactElement) => <AppsLayout>{page}</AppsLayout>;
@@ -1,14 +1,11 @@
import React, { ComponentProps } from "react";
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
import { InstalledAppVariants } from "@calcom/app-store/utils";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "../../../Icon";
import Shell from "../Shell";
import type { HorizontalTabItemProps } from "../navigation/tabs/HorizontalTabItem";
import HorizontalTabs from "../navigation/tabs/HorizontalTabs";
import type { VerticalTabItemProps } from "../navigation/tabs/VerticalTabItem";
import VerticalTabs from "../navigation/tabs/VerticalTabs";
import { Icon, Shell } from "@calcom/ui";
import type { HorizontalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/HorizontalTabItem";
import type { VerticalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/VerticalTabItem";
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{
@@ -47,25 +44,22 @@ export default function InstalledAppsLayout({
children,
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const query = trpc.useQuery([
"viewer.integrations",
{ variant: InstalledAppVariants.payment, onlyInstalled: true },
]);
const query = trpc.viewer.integrations.useQuery({
variant: InstalledAppVariants.payment,
onlyInstalled: true,
});
let actualTabs = tabs;
if (query.data?.items.length === 0) {
actualTabs = tabs.filter((tab) => tab.name !== InstalledAppVariants.payment);
}
return (
<Shell {...rest}>
<div className="flex flex-col p-2 md:p-0 xl:flex-row">
<div className="hidden xl:block">
<VerticalTabs tabs={actualTabs} sticky linkProps={{ shallow: true }} />
</div>
<div className="block xl:hidden">
<HorizontalTabs tabs={actualTabs} linkProps={{ shallow: true }} />
</div>
<main className="w-full xl:mx-5 xl:w-4/5 xl:pr-5">{children}</main>
</div>
<AppCategoryNavigation
baseURL="/apps/installed"
containerClassname="w-full xl:mx-5 xl:w-4/5 xl:max-w-2xl xl:pr-5">
{children}
</AppCategoryNavigation>
</Shell>
);
}
+1 -1
View File
@@ -3,7 +3,7 @@ import useDigitInput from "react-digit-input";
import { useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Label, Input } from "@calcom/ui/form/fields";
import { Label, Input } from "@calcom/ui";
export default function TwoFactor({ center = true }) {
const [value, onChange] = useState("");
@@ -2,9 +2,9 @@ import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import React, { ComponentProps, useEffect } from "react";
import ErrorBoundary from "../../../ErrorBoundary";
import Shell from "../Shell";
import SettingsLayout from "./SettingsLayout";
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
import { ErrorBoundary, Shell } from "@calcom/ui";
import { UserPermissionRole } from ".prisma/client";
export default function AdminLayout({
@@ -16,14 +16,14 @@ export default function AdminLayout({
// Force redirect on component level
useEffect(() => {
if (session.data?.user.role !== UserPermissionRole.ADMIN) {
router.replace("/settings");
if (session.data && session.data.user.role !== UserPermissionRole.ADMIN) {
router.replace("/settings/my-account/profile");
}
}, [session, router]);
return (
<SettingsLayout {...rest}>
<div className="mx-auto flex max-w-4xl flex-row divide-y divide-gray-200 md:px-12">
<div className="mx-auto flex max-w-4xl flex-row divide-y divide-gray-200">
<div className="flex flex-1 [&>*]:flex-1">
<ErrorBoundary>{children}</ErrorBoundary>
</div>
@@ -1,6 +1,6 @@
import React from "react";
import { SkeletonText } from "@calcom/ui/v2";
import { SkeletonText } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -1,10 +1,13 @@
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import Tooltip from "@calcom/ui/v2/core/Tooltip";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon, Tooltip } from "@calcom/ui";
import { Props } from "./pages/AvailabilityPage";
export function AvailableEventLocations({ locations }: { locations: Props["eventType"]["locations"] }) {
const { t } = useLocale();
return locations.length ? (
<div className="dark:text-darkgray-600 mr-6 flex w-full flex-col space-y-2 break-words text-sm text-gray-600">
{locations.map((location) => {
@@ -15,16 +18,20 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
}
return (
<div key={location.type} className="flex flex-row items-center text-sm font-medium">
<img
src={eventLocationType.iconUrl}
className={classNames(
"mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 ",
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
<Tooltip content={locationKeyToString(location)}>
<p className="truncate">{locationKeyToString(location)}</p>
{eventLocationType.iconUrl === "/link.svg" ? (
<Icon.FiLink className="dark:text-darkgray-600 ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 " />
) : (
<img
src={eventLocationType.iconUrl}
className={classNames(
"ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 ",
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
)}
<Tooltip content={t(locationKeyToString(location) ?? "")}>
<p className="truncate">{t(locationKeyToString(location) ?? "")}</p>
</Tooltip>
</div>
);
+31 -31
View File
@@ -7,8 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TimeFormat } from "@calcom/lib/timeFormat";
import { nameOfDay } from "@calcom/lib/weekday";
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
import { ToggleGroup } from "@calcom/ui/v2/core/form/ToggleGroup";
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
import classNames from "@lib/classNames";
import { timeZone } from "@lib/clock";
@@ -58,7 +57,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
{nameOfDay(i18n.language, Number(date.format("d")), "short")}
</span>
<span className="text-bookinglight font-medium">
, {date.toDate().toLocaleString(i18n.language, { month: "long" })} {date.format(" D ")}
, {date.toDate().toLocaleString(i18n.language, { month: "short" })} {date.format(" D ")}
</span>
</div>
<div className="ml-auto">
@@ -72,7 +71,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
/>
</div>
</div>
<div className="-mb-5 grid flex-grow grid-cols-1 gap-x-2 overflow-y-auto sm:block md:h-[364px]">
<div className="flex-grow overflow-y-auto sm:block md:h-[364px]">
{slots.length > 0 &&
slots.map((slot) => {
type BookingURL = {
@@ -102,7 +101,8 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
}
return (
<div key={dayjs(slot.time).format()}>
<div data-slot-owner={(slot.userIds || []).join(",")} key={`${dayjs(slot.time).format()}`}>
{/* ^ data-slot-owner is helpful in debugging and used to identify the owners of the slot. Owners are the users which have the timeslot in their schedule. It doesn't consider if a user has that timeslot booked */}
{/* Current there is no way to disable Next.js Links */}
{seatsPerTimeSlot && slot.attendees && slot.attendees >= seatsPerTimeSlot ? (
<div
@@ -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 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>
)}
</Link>
)}
</div>
@@ -152,13 +152,13 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
{isLoading && !slots.length && (
<>
<SkeletonContainer className="mb-2">
<SkeletonText width="full" height="20" />
<SkeletonText className="h-5 w-full" />
</SkeletonContainer>
<SkeletonContainer className="mb-2">
<SkeletonText width="full" height="20" />
<SkeletonText className="h-5 w-full" />
</SkeletonContainer>
<SkeletonContainer className="mb-2">
<SkeletonText width="full" height="20" />
<SkeletonText className="h-5 w-full" />
</SkeletonContainer>
</>
)}
@@ -1,9 +1,12 @@
import { SchedulingType } from "@prisma/client";
import { FC, ReactNode } from "react";
import { FC, ReactNode, useEffect } from "react";
import dayjs from "@calcom/dayjs";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Icon, Badge } from "@calcom/ui";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { UserAvatars } from "@components/booking/UserAvatars";
import EventTypeDescriptionSafeHTML from "@components/eventtype/EventTypeDescriptionSafeHTML";
@@ -39,7 +42,34 @@ interface Props {
const BookingDescription: FC<Props> = (props) => {
const { profile, eventType, isBookingPage = false, children } = props;
const { date: bookingDate } = useRouterQuery("date");
const { t } = useLocale();
const { duration = eventType.length.toString(), setQuery: setDuration } = useRouterQuery("duration");
useEffect(() => {
if (
eventType.metadata?.multipleDuration &&
!eventType.metadata?.multipleDuration?.includes(Number(duration))
) {
setDuration(eventType.length.toString());
}
}, [duration, setDuration, eventType.length, eventType.metadata?.multipleDuration]);
let requiresConfirmation = eventType?.requiresConfirmation;
let requiresConfirmationText = t("requires_confirmation");
const rcThreshold = eventType?.metadata?.requiresConfirmationThreshold;
if (rcThreshold) {
if (isBookingPage) {
if (dayjs(bookingDate).diff(dayjs(), rcThreshold.unit) > rcThreshold.time) {
requiresConfirmation = false;
}
} else {
requiresConfirmationText = t("requires_confirmation_threshold", {
...rcThreshold,
unit: rcThreshold.unit.slice(0, -1),
});
}
}
return (
<>
<UserAvatars
@@ -55,7 +85,7 @@ const BookingDescription: FC<Props> = (props) => {
<h1 className="font-cal dark:text-darkgray-900 mb-6 break-words text-2xl font-semibold text-gray-900">
{eventType.title}
</h1>
<div className="dark:text-darkgray-600 flex flex-col space-y-3 text-sm font-medium text-gray-600">
<div className="dark:text-darkgray-600 flex flex-col space-y-4 text-sm font-medium text-gray-600">
{eventType?.description && (
<div
className={classNames(
@@ -65,7 +95,7 @@ const BookingDescription: FC<Props> = (props) => {
<div>
<Icon.FiInfo
className={classNames(
"mr-[10px] ml-[2px] inline-block h-4 w-4",
"ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]",
isBookingPage && "dark:text-darkgray-600 -mt-1 text-gray-500"
)}
/>
@@ -75,29 +105,61 @@ const BookingDescription: FC<Props> = (props) => {
</div>
</div>
)}
{eventType?.requiresConfirmation && (
{requiresConfirmation && (
<div
className={classNames(
"flex items-center",
"items-top flex",
isBookingPage && "dark:text-darkgray-600 text-sm font-medium text-gray-600"
)}>
<div>
<Icon.FiCheckSquare className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 " />
<Icon.FiCheckSquare className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
</div>
{t("requires_confirmation")}
{requiresConfirmationText}
</div>
)}
<AvailableEventLocations
locations={eventType.locations as AvailabilityPageProps["eventType"]["locations"]}
/>
<p
<div
className={classNames(
"text-sm font-medium",
"flex flex-nowrap text-sm font-medium",
isBookingPage && "dark:text-darkgray-600 text-gray-600"
)}>
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
</p>
<Icon.FiClock
className={classNames(
"min-h-4 min-w-4 ml-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]",
isBookingPage && "mt-[2px]"
)}
/>
{eventType.metadata?.multipleDuration !== undefined ? (
!isBookingPage ? (
<ul className="-mt-1 flex flex-wrap gap-1">
{eventType.metadata.multipleDuration.map((dur, idx) => (
<li key={idx}>
<Badge
variant="gray"
size="lg"
className={classNames(
duration === dur.toString()
? "bg-darkgray-200 text-darkgray-900 dark:bg-darkmodebrand dark:!text-darkmodebrandcontrast"
: "hover:bg-darkgray-200 dark:hover:bg-darkmodebrand hover:text-darkgray-900 dark:hover:text-darkmodebrandcontrast dark:bg-darkgray-200 bg-gray-200 text-gray-900 dark:text-white",
"cursor-pointer"
)}
onClick={() => {
setDuration(dur);
}}>
{dur} {t("minute_timeUnit")}
</Badge>
</li>
))}
</ul>
) : (
`${duration} ${t("minutes")}`
)
) : (
`${eventType.length} ${t("minutes")}`
)}
</div>
{children}
</div>
</>
+108 -101
View File
@@ -1,36 +1,44 @@
import { BookingStatus } from "@prisma/client";
import { useRouter } from "next/router";
import { useState, useMemo } from "react";
import { useState } from "react";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
import ViewRecordingsDialog from "@calcom/features/ee/video/ViewRecordingsDialog";
import classNames from "@calcom/lib/classNames";
import { formatTime } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { inferQueryInput, inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { TextArea } from "@calcom/ui/form/fields";
import MeetingTimeInTimezones from "@calcom/ui/v2/core/MeetingTimeInTimezones";
import Tooltip from "@calcom/ui/v2/core/Tooltip";
import showToast from "@calcom/ui/v2/core/notifications";
import { RouterInputs, RouterOutputs, trpc } from "@calcom/trpc/react";
import {
Badge,
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
Icon,
MeetingTimeInTimezones,
showToast,
TextArea,
Tooltip,
ActionType,
TableActions,
} from "@calcom/ui";
import useMeQuery from "@lib/hooks/useMeQuery";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
import TableActions, { ActionType } from "@components/ui/TableActions";
type BookingListingStatus = inferQueryInput<"viewer.bookings">["status"];
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["filters"]["status"];
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
type BookingItemProps = BookingItem & {
listingStatus: BookingListingStatus;
recurringInfo: inferQueryOutput<"viewer.bookings">["recurringInfo"][number] | undefined;
recurringInfo: RouterOutputs["viewer"]["bookings"]["get"]["recurringInfo"][number] | undefined;
};
function BookingListItem(booking: BookingItemProps) {
@@ -42,15 +50,20 @@ function BookingListItem(booking: BookingItemProps) {
const router = useRouter();
const [rejectionReason, setRejectionReason] = useState<string>("");
const [rejectionDialogIsOpen, setRejectionDialogIsOpen] = useState(false);
const mutation = trpc.useMutation(["viewer.bookings.confirm"], {
onSuccess: () => {
setRejectionDialogIsOpen(false);
showToast(t("booking_confirmation_success"), "success");
utils.invalidateQueries("viewer.bookings");
const [viewRecordingsDialogIsOpen, setViewRecordingsDialogIsOpen] = useState<boolean>(false);
const mutation = trpc.viewer.bookings.confirm.useMutation({
onSuccess: (data) => {
if (data.status === BookingStatus.REJECTED) {
setRejectionDialogIsOpen(false);
showToast(t("booking_rejection_success"), "success");
} else {
showToast(t("booking_confirmation_success"), "success");
}
utils.viewer.bookings.invalidate();
},
onError: () => {
showToast(t("booking_confirmation_failed"), "error");
utils.invalidateQueries("viewer.bookings");
utils.viewer.bookings.invalidate();
},
});
@@ -102,13 +115,26 @@ function BookingListItem(booking: BookingItemProps) {
},
];
const showRecordingActions: ActionType[] = [
{
id: "view_recordings",
label: t("view_recordings"),
onClick: () => {
setViewRecordingsDialogIsOpen(true);
},
disabled: mutation.isLoading,
},
];
let bookedActions: ActionType[] = [
{
id: "cancel",
label: isTabRecurring && isRecurring ? t("cancel_all_remaining") : t("cancel"),
/* When cancelling we need to let the UI and the API know if the intention is to
cancel all remaining bookings or just that booking instance. */
href: `/cancel/${booking.uid}${isTabRecurring && isRecurring ? "?allRemainingBookings=true" : ""}`,
href: `/booking/${booking.uid}?cancel=true${
isTabRecurring && isRecurring ? "&allRemainingBookings=true" : ""
}`,
icon: Icon.FiX,
},
{
@@ -146,6 +172,10 @@ function BookingListItem(booking: BookingItemProps) {
bookedActions = bookedActions.filter((action) => action.id !== "edit_booking");
}
if (isPast && isPending && !isConfirmed) {
bookedActions = bookedActions.filter((action) => action.id !== "cancel");
}
const RequestSentMessage = () => {
return (
<div className="ml-1 mr-8 flex text-gray-500" data-testid="request_reschedule_sent">
@@ -158,11 +188,11 @@ function BookingListItem(booking: BookingItemProps) {
const startTime = dayjs(booking.startTime).format(isUpcoming ? "ddd, D MMM" : "D MMMM YYYY");
const [isOpenRescheduleDialog, setIsOpenRescheduleDialog] = useState(false);
const [isOpenSetLocationDialog, setIsOpenLocationDialog] = useState(false);
const setLocationMutation = trpc.useMutation("viewer.bookings.editLocation", {
const setLocationMutation = trpc.viewer.bookings.editLocation.useMutation({
onSuccess: () => {
showToast(t("location_updated"), "success");
setIsOpenLocationDialog(false);
utils.invalidateQueries("viewer.bookings");
utils.viewer.bookings.invalidate();
},
});
@@ -181,30 +211,16 @@ function BookingListItem(booking: BookingItemProps) {
.concat(booking.recurringInfo?.bookings[BookingStatus.PENDING])
.sort((date1: Date, date2: Date) => date1.getTime() - date2.getTime());
const location = booking.location || "";
const onClickTableData = () => {
router.push({
pathname: "/success",
pathname: `/booking/${booking.uid}`,
query: {
date: booking.startTime,
// TODO: Booking when fetched should have id 0 already(for Dynamic Events).
type: booking.eventType.id || 0,
eventSlug: booking.eventType.slug,
username: user?.username || "",
name: booking.attendees[0] ? booking.attendees[0].name : undefined,
allRemainingBookings: isTabRecurring,
email: booking.attendees[0] ? booking.attendees[0].email : undefined,
location: location,
eventName: booking.eventType.eventName || "",
bookingId: booking.id,
recur: booking.recurringEventId,
reschedule: isConfirmed,
listingStatus: booking.listingStatus,
status: booking.status,
},
});
};
const showRecordingsButtons = booking.location === "integrations:daily" && isPast && isConfirmed;
return (
<>
<RescheduleDialog
@@ -218,7 +234,14 @@ function BookingListItem(booking: BookingItemProps) {
isOpenDialog={isOpenSetLocationDialog}
setShowLocationModal={setIsOpenLocationDialog}
/>
{showRecordingsButtons && (
<ViewRecordingsDialog
booking={booking}
isOpenDialog={viewRecordingsDialogIsOpen}
setIsOpenDialog={setViewRecordingsDialogIsOpen}
timeFormat={user?.timeFormat ?? null}
/>
)}
{/* NOTE: Should refactor this dialog component as is being rendered multiple times */}
<Dialog open={rejectionDialogIsOpen} onOpenChange={setRejectionDialogIsOpen}>
<DialogContent>
@@ -237,9 +260,7 @@ function BookingListItem(booking: BookingItemProps) {
/>
<DialogFooter>
<DialogClose>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<DialogClose />
<Button
disabled={mutation.isLoading}
@@ -252,7 +273,7 @@ function BookingListItem(booking: BookingItemProps) {
</DialogContent>
</Dialog>
<tr className="flex flex-col hover:bg-neutral-50 sm:flex-row">
<tr className="group flex flex-col hover:bg-neutral-50 sm:flex-row">
<td
className="hidden align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:min-w-[12rem]"
onClick={onClickTableData}>
@@ -279,9 +300,9 @@ function BookingListItem(booking: BookingItemProps) {
{booking.eventType.team.name}
</Badge>
)}
{!!booking?.eventType?.price && !booking.paid && (
<Badge className="ltr:mr-2 rtl:ml-2" variant="orange">
{t("pending_payment")}
{booking.paid && (
<Badge className="ltr:mr-2 rtl:ml-2" variant="green">
{t("paid")}
</Badge>
)}
{recurringDates !== undefined && (
@@ -342,12 +363,14 @@ function BookingListItem(booking: BookingItemProps) {
<span> </span>
{!!booking?.eventType?.price && !booking.paid && (
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">Pending payment</Tag>
<Badge className="hidden ltr:ml-2 ltr:mr-2 rtl:ml-2 sm:inline-flex" variant="orange">
{t("pending_payment")}
</Badge>
)}
</div>
{booking.description && (
<div
className="max-w-10/12 sm:max-w-40 md:max-w-56 xl:max-w-80 lg:max-w-64 truncate text-sm text-gray-600"
className="max-w-10/12 sm:max-w-32 md:max-w-52 xl:max-w-80 truncate text-sm text-gray-600"
title={booking.description}>
&quot;{booking.description}&quot;
</div>
@@ -375,6 +398,7 @@ function BookingListItem(booking: BookingItemProps) {
</>
) : null}
{isPast && isPending && !isConfirmed ? <TableActions actions={bookedActions} /> : null}
{showRecordingsButtons && <TableActions actions={showRecordingActions} />}
{isCancelled && booking.rescheduled && (
<div className="hidden h-full items-center md:flex">
<RequestSentMessage />
@@ -467,8 +491,9 @@ const FirstAttendee = ({
user: UserProps;
currentEmail: string | null | undefined;
}) => {
const { t } = useLocale();
return user.email === currentEmail ? (
<div className="inline-block">You</div>
<div className="inline-block">{t("you")}</div>
) : (
<a
key={user.email}
@@ -480,19 +505,19 @@ const FirstAttendee = ({
);
};
const Attendee: React.FC<{ email: string; children: React.ReactNode }> = ({ email, children }) => {
type AttendeeProps = {
name?: string;
email: string;
};
const Attendee = ({ email, name }: AttendeeProps) => {
return (
<a className=" hover:text-blue-500" href={"mailto:" + email} onClick={(e) => e.stopPropagation()}>
{children}
<a className="hover:text-blue-500" href={"mailto:" + email} onClick={(e) => e.stopPropagation()}>
{name || email}
</a>
);
};
interface AttendeeProps {
name: string;
email: string;
}
const DisplayAttendees = ({
attendees,
user,
@@ -500,50 +525,32 @@ const DisplayAttendees = ({
}: {
attendees: AttendeeProps[];
user: UserProps | null;
currentEmail: string | null | undefined;
currentEmail?: string | null;
}) => {
if (attendees.length === 1) {
return (
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
<span>&nbsp;and&nbsp;</span>
<Attendee email={attendees[0].email}>{attendees[0].name}</Attendee>
</div>
);
} else if (attendees.length === 2) {
return (
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
<span>,&nbsp;</span>
<Attendee email={attendees[0].email}>{attendees[0].name}</Attendee>
<div className="inline-block text-sm text-gray-900">&nbsp;and&nbsp;</div>
<Attendee email={attendees[1].email}>{attendees[1].name}</Attendee>
</div>
);
} else {
return (
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
<span>,&nbsp;</span>
<Attendee email={attendees[0].email}>{attendees[0].name}</Attendee>
<span>&nbsp;&&nbsp;</span>
<Tooltip
content={attendees.slice(1).map((attendee, key) => (
<p key={key}>{attendee.name}</p>
))}>
<div className="inline-block">{attendees.length - 1} more</div>
</Tooltip>
</div>
);
}
};
const Tag = ({ children, className = "" }: React.PropsWithChildren<{ className?: string }>) => {
const { t } = useLocale();
return (
<span
className={`inline-flex items-center rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-800 ${className}`}>
{children}
</span>
<div className="text-sm text-gray-900">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
{attendees.length > 1 ? <span>,&nbsp;</span> : <span>&nbsp;{t("and")}&nbsp;</span>}
<Attendee {...attendees[0]} />
{attendees.length > 1 && (
<>
<div className="inline-block text-sm text-gray-900">&nbsp;{t("and")}&nbsp;</div>
{attendees.length > 2 ? (
<Tooltip
content={attendees.slice(1).map((attendee) => (
<p key={attendee.email}>
<Attendee {...attendee} />
</p>
))}>
<div className="inline-block">{t("plus_more", { count: attendees.length - 1 })}</div>
</Tooltip>
) : (
<Attendee {...attendees[1]} />
)}
</>
)}
</div>
);
};
+16 -26
View File
@@ -5,8 +5,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Button } from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import { Button, Icon, TextArea } from "@calcom/ui";
type Props = {
booking: {
@@ -22,13 +21,14 @@ type Props = {
team?: string | null;
setIsCancellationMode: (value: boolean) => void;
theme: string | null;
allRemainingBookings: boolean;
};
export default function CancelBooking(props: Props) {
const [cancellationReason, setCancellationReason] = useState<string>("");
const { t } = useLocale();
const router = useRouter();
const { booking, profile, team } = props;
const { booking, profile, team, allRemainingBookings } = props;
const [loading, setLoading] = useState(false);
const telemetry = useTelemetry();
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
@@ -37,7 +37,7 @@ export default function CancelBooking(props: Props) {
return (
<>
{error && (
<div>
<div className="mt-8">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<Icon.FiX className="h-6 w-6 text-red-600" />
</div>
@@ -51,30 +51,23 @@ export default function CancelBooking(props: Props) {
{!error && (
<div className="mt-5 sm:mt-6">
<label className="text-bookingdark font-medium dark:text-white">{t("cancellation_reason")}</label>
<textarea
<TextArea
placeholder={t("cancellation_reason_placeholder")}
value={cancellationReason}
onChange={(e) => setCancellationReason(e.target.value)}
className="mt-2 mb-3 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:mb-3 "
className="mt-2 mb-4 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white "
rows={3}
/>
<div className="flex flex-col-reverse rtl:space-x-reverse sm:flex-row">
{!props.recurringEvent && (
<div className="border-bookinglightest mt-5 flex w-full justify-center border-t pt-3 sm:mt-0 sm:justify-start sm:border-0 sm:pt-0">
<Button
color="secondary"
className="border-0 sm:border"
onClick={() => router.push(`/reschedule/${booking?.uid}`)}>
{t("reschedule_this")}
</Button>
</div>
)}
<div className="mb-2 flex w-full space-x-2 text-right sm:mb-0 ">
<Button color="secondary" onClick={() => props.setIsCancellationMode(false)}>
<div className="flex flex-col-reverse rtl:space-x-reverse ">
<div className="ml-auto flex w-full space-x-4 ">
<Button
className="ml-auto"
color="secondary"
onClick={() => props.setIsCancellationMode(false)}>
{t("nevermind")}
</Button>
<Button
className="flex grow justify-center"
className="flex justify-center"
data-testid="cancel"
onClick={async () => {
setLoading(true);
@@ -82,6 +75,7 @@ export default function CancelBooking(props: Props) {
const payload = {
id: booking?.id,
cancellationReason: cancellationReason,
allRemainingBookings,
};
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());
@@ -95,11 +89,7 @@ export default function CancelBooking(props: Props) {
});
if (res.status >= 200 && res.status < 300) {
await router.push(
`/cancel/success?name=${props.profile.name}&title=${booking?.title}&eventPage=${
profile.slug
}&team=${team ? 1 : 0}&recurring=${!!props.recurringEvent}`
);
await router.replace(router.asPath);
} else {
setLoading(false);
setError(
@@ -110,7 +100,7 @@ export default function CancelBooking(props: Props) {
}
}}
loading={loading}>
{t("cancel_event")}
{props.allRemainingBookings ? t("cancel_all_remaining") : t("cancel_event")}
</Button>
</div>
</div>
@@ -1,6 +1,6 @@
import React from "react";
import { SkeletonText } from "@calcom/ui/v2";
import { SkeletonText } from "@calcom/ui";
function SkeletonLoader() {
return (
+1 -1
View File
@@ -1,7 +1,7 @@
import { FC, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import TimezoneSelect, { ITimezoneOption } from "@calcom/ui/v2/core/TimezoneSelect";
import { ITimezoneOption, TimezoneSelect } from "@calcom/ui";
import { timeZone } from "../../lib/clock";
+14 -3
View File
@@ -7,7 +7,7 @@ export const UserAvatars = ({
users,
...props
}: {
profile: { image: string | null; name?: string | null };
profile: { image: string | null; name?: string | null; username?: string | null };
showMembers: boolean;
users: { username: string | null; name?: string | null }[];
} & Pick<AvatarGroupProps, "size" | "truncateAfter">) => {
@@ -17,15 +17,26 @@ export const UserAvatars = ({
border="border-2 dark:border-darkgray-100 border-white"
items={
[
{ image: profile.image, alt: profile.name, title: profile.name },
{
image: profile.image,
alt: profile.name,
title: profile.name,
href: profile.username ? `${CAL_URL}/${profile.username}` : undefined,
},
...(showMembers
? users.map((user) => ({
title: user.name,
image: `${CAL_URL}/${user.username}/avatar.png`,
alt: user.name || undefined,
href: user.username ? `${CAL_URL}/${user.username}` : undefined,
}))
: []),
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
].filter((item) => !!item.image) as {
image: string;
alt?: string;
title?: string;
href?: string;
}[]
}
size={props.size}
truncateAfter={props.truncateAfter}
@@ -1,7 +1,6 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventType } from "@prisma/client";
import * as Popover from "@radix-ui/react-popover";
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { useReducer, useEffect, useMemo, useState } from "react";
import { Toaster } from "react-hot-toast";
@@ -14,12 +13,13 @@ import dayjs, { Dayjs } from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
useEmbedStyles,
useEmbedUiConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import DatePicker from "@calcom/features/calendars/DatePicker";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { WEBSITE_URL } from "@calcom/lib/constants";
import getStripeAppData from "@calcom/lib/getStripeAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
@@ -28,42 +28,21 @@ 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/Icon";
import DatePicker from "@calcom/ui/v2/modules/booker/DatePicker";
import { Icon, HeadSeo } from "@calcom/ui";
import { timeZone as localStorageTimeZone } from "@lib/clock";
// import { timeZone } from "@lib/clock";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import useRouterQuery from "@lib/hooks/useRouterQuery";
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]";
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
// Get router variables
const GoBackToPreviousPage = ({ t }: { t: TFunction }) => {
const router = useRouter();
const path = router.asPath.split("/");
path.pop(); // Remove the last item (where we currently are)
path.shift(); // Removes first item e.g. if we were visitng "/teams/test/30mins" the array will new look like ["teams","test"]
const slug = path.join("/");
return (
<div className="flex h-full flex-col justify-end">
<button title={t("profile")} onClick={() => router.replace(`${WEBSITE_URL}/${slug}`)}>
<Icon.FiArrowLeft className="dark:text-darkgray-600 h-4 w-4 text-black transition-opacity hover:cursor-pointer" />
<p className="sr-only">Go Back</p>
</button>
</div>
);
};
const useSlots = ({
eventTypeId,
eventTypeSlug,
@@ -71,6 +50,7 @@ const useSlots = ({
endTime,
usernameList,
timeZone,
duration,
}: {
eventTypeId: number;
eventTypeSlug: string;
@@ -78,20 +58,21 @@ const useSlots = ({
endTime?: Dayjs;
usernameList: string[];
timeZone?: string;
duration?: string;
}) => {
const { data, isLoading, isPaused } = trpc.useQuery(
[
"viewer.public.slots.getSchedule",
{
eventTypeId,
eventTypeSlug,
usernameList,
startTime: startTime?.toISOString() || "",
endTime: endTime?.toISOString() || "",
timeZone,
},
],
{ enabled: !!startTime && !!endTime }
const { data, isLoading, isPaused } = trpc.viewer.public.slots.getSchedule.useQuery(
{
eventTypeId,
eventTypeSlug,
usernameList,
startTime: startTime?.toISOString() || "",
endTime: endTime?.toISOString() || "",
timeZone,
duration,
},
{
enabled: !!startTime && !!endTime,
}
);
const [cachedSlots, setCachedSlots] = useState<NonNullable<typeof data>["slots"]>({});
@@ -128,6 +109,7 @@ const SlotPicker = ({
}) => {
const [selectedDate, setSelectedDate] = useState<Dayjs>();
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
const { duration } = useRouterQuery("duration");
const { date, setQuery: setDate } = useRouterQuery("date");
const { month, setQuery: setMonth } = useRouterQuery("month");
const router = useRouter();
@@ -153,7 +135,7 @@ const SlotPicker = ({
setSelectedDate(dayjs.tz(date, timeZone));
}
}
}, [router.isReady, month, date, timeZone]);
}, [router.isReady, month, date, duration, timeZone]);
const { i18n, isLocaleReady } = useLocale();
const { slots: _1 } = useSlots({
@@ -163,14 +145,19 @@ const SlotPicker = ({
startTime: selectedDate?.startOf("day"),
endTime: selectedDate?.endOf("day"),
timeZone,
duration,
});
const { slots: _2, isLoading } = useSlots({
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
usernameList: users,
startTime: browsingDate?.startOf("month"),
startTime:
browsingDate === undefined || browsingDate.get("month") === dayjs.tz(undefined, timeZone).get("month")
? dayjs.tz(undefined, timeZone).subtract(2, "days").startOf("day")
: browsingDate?.startOf("month"),
endTime: browsingDate?.endOf("month"),
timeZone,
duration,
});
const slots = useMemo(() => ({ ..._2, ..._1 }), [_1, _2]);
@@ -231,14 +218,14 @@ function TimezoneDropdown({
return (
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 inline-block rounded-md px-2 py-2 text-left text-gray-600">
<p className="text-sm font-medium">
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-[2px] inline-block h-4 w-4" />
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 !mt-2 inline-block self-start rounded-md px-2 py-2 text-left text-gray-600">
<p className="flex items-center text-sm font-medium">
<Icon.FiGlobe className="min-h-4 min-w-4 ml-[2px] -mt-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]" />
{timeZone}
{isTimeOptionsOpen ? (
<Icon.FiChevronUp className="ml-1 inline-block h-4 w-4" />
<Icon.FiChevronUp className="min-h-4 min-w-4 ml-1 inline-block" />
) : (
<Icon.FiChevronDown className="ml-1 inline-block h-4 w-4" />
<Icon.FiChevronDown className="min-h-4 min-w-4 ml-1 inline-block" />
)}
</p>
</Popover.Trigger>
@@ -260,23 +247,6 @@ const dateQuerySchema = z.object({
timeZone: z.string().optional().default(""),
});
const useRouterQuery = <T extends string>(name: T) => {
const router = useRouter();
const existingQueryParams = router.asPath.split("?")[1];
const urlParams = new URLSearchParams(existingQueryParams);
const query = Object.fromEntries(urlParams);
const setQuery = (newValue: string | number | null | undefined) => {
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
router.replace({ query: { ...router.query, ...query, [name]: newValue } }, undefined, { shallow: true });
};
return { [name]: query[name], setQuery } as {
[K in T]: string | undefined;
} & { setQuery: typeof setQuery };
};
export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
@@ -287,6 +257,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
useTheme(profile.theme);
const { t } = useLocale();
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const isBackgroundTransparent = useIsBackgroundTransparent();
@@ -311,9 +282,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
}, []);
// TODO: Improve this;
useExposePlanGlobally(eventType.users.length === 1 ? eventType.users[0].plan : "PRO");
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
const telemetry = useTelemetry();
@@ -326,7 +294,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
);
}
}, [telemetry]);
const embedUiConfig = useEmbedUiConfig();
// get dynamic user list here
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
@@ -339,6 +307,8 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
// Define conditional gates here
const gates = [
// Rainbow gate is only added if the event has both a `blockchainId` and a `smartContractAddress`
@@ -372,7 +342,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
<div>
<main
className={classNames(
"flex-col md:mx-4 lg:flex",
"flex flex-col md:mx-4",
shouldAlignCentrally ? "items-center" : "items-start",
!isEmbed && classNames("mx-auto my-0 ease-in-out md:my-24")
)}>
@@ -387,60 +357,55 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
isEmbed && "mx-auto"
)}>
<div className="overflow-hidden md:flex">
<div
className={classNames(
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
"min-w-full md:w-[230px] md:min-w-[230px]",
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
)}>
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
{showEventTypeDetails && (
<div
className={classNames(
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
"min-w-full md:w-[230px] md:min-w-[230px]",
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
)}>
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
</div>
</div>
</div>
)}
{stripeAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{timezoneDropdown}
</BookingDescription>
)}
{stripeAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{timezoneDropdown}
</BookingDescription>
{!isEmbed && (
<div className="mt-auto hidden md:block">
<GoBackToPreviousPage t={t} />
</div>
)}
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-4 mb-3 text-gray-600 dark:text-darkgray-600"
@@ -448,12 +413,13 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-darkgray-600">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-500" />
<CalendarIcon className="ltr:mr-[10px] rtl:ml-[10px] -mt-1 inline-block h-4 w-4 text-gray-500" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)*/}
</div>
</div>
)}
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
@@ -479,7 +445,8 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
/>
</div>
</div>
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}
{/* FIXME: We don't show branding in Embed yet because we need to place branding on top of the main content. Keeping it outside the main content would have visibility issues because outside main content background is transparent */}
{!restProps.isBrandingHidden && !isEmbed && <PoweredByCal />}
</div>
</main>
</div>
+230 -161
View File
@@ -5,7 +5,7 @@ import { isValidPhoneNumber } from "libphonenumber-js";
import { useSession } from "next-auth/react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState, useReducer } from "react";
import { useEffect, useMemo, useReducer, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { FormattedNumber, IntlProvider } from "react-intl";
import { ReactMultiEmail } from "react-multi-email";
@@ -14,11 +14,11 @@ import { z } from "zod";
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
import {
locationKeyToString,
getEventLocationValue,
getEventLocationType,
EventLocationType,
getEventLocationType,
getEventLocationValue,
getHumanReadableLocationValue,
locationKeyToString,
} from "@calcom/app-store/locations";
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
import { getEventTypeAppData } from "@calcom/app-store/utils";
@@ -26,28 +26,26 @@ import { LocationObject, LocationType } from "@calcom/core/location";
import dayjs from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
useEmbedUiConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { APP_NAME } from "@calcom/lib/constants";
import getStripeAppData from "@calcom/lib/getStripeAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { HttpError } from "@calcom/lib/http-error";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Icon } from "@calcom/ui/Icon";
import { Tooltip } from "@calcom/ui/Tooltip";
import { Button } from "@calcom/ui/components";
import AddressInput from "@calcom/ui/form/AddressInputLazy";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { EmailInput, Form } from "@calcom/ui/form/fields";
import { AddressInput, Button, EmailInput, Form, Icon, PhoneInput, Tooltip } from "@calcom/ui";
import { Group, RadioField } from "@calcom/ui";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
import useMeQuery from "@lib/hooks/useMeQuery";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import createBooking from "@lib/mutations/bookings/create-booking";
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
import { parseDate, parseRecurringDates } from "@lib/parseDate";
@@ -90,10 +88,9 @@ const BookingPage = ({
...restProps
}: BookingPageProps) => {
const { t, i18n } = useLocale();
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
const user = query.data;
const { duration: queryDuration } = useRouterQuery("duration");
const isEmbed = useIsEmbed(restProps.isEmbed);
const embedUiConfig = useEmbedUiConfig();
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const router = useRouter();
@@ -108,6 +105,11 @@ const BookingPage = ({
{}
);
const stripeAppData = getStripeAppData(eventType);
// Define duration now that we support multiple duration eventTypes
let duration = eventType.length;
if (queryDuration && !isNaN(Number(queryDuration))) {
duration = Number(queryDuration);
}
useEffect(() => {
if (top !== window) {
@@ -122,7 +124,7 @@ const BookingPage = ({
const mutation = useMutation(createBooking, {
onSuccess: async (responseData) => {
const { id, paymentUid } = responseData;
const { uid, paymentUid } = responseData;
if (paymentUid) {
return await router.push(
createPaymentLink({
@@ -136,19 +138,12 @@ const BookingPage = ({
}
return router.push({
pathname: "/success",
pathname: `/booking/${uid}`,
query: {
date,
type: eventType.id,
eventSlug: eventType.slug,
username: profile.slug,
reschedule: !!rescheduleUid,
name: bookingForm.getValues("name"),
email: bookingForm.getValues("email"),
location: responseData.location,
eventName: profile.eventName || "",
bookingId: id,
isSuccessBookingPage: true,
email: bookingForm.getValues("email"),
eventTypeSlug: eventType.slug,
formerTime: booking?.startTime.toString(),
},
});
},
@@ -156,31 +151,15 @@ const BookingPage = ({
const recurringMutation = useMutation(createRecurringBooking, {
onSuccess: async (responseData = []) => {
const { attendees = [], id, recurringEventId } = responseData[0] || {};
const location = (function humanReadableLocation(location) {
if (!location) {
return;
}
if (location.includes("integration")) {
return t("web_conferencing_details_to_follow");
}
return location;
})(responseData[0].location);
const { uid } = responseData[0] || {};
return router.push({
pathname: "/success",
pathname: `/booking/${uid}`,
query: {
date,
type: eventType.id,
eventSlug: eventType.slug,
recur: recurringEventId,
username: profile.slug,
reschedule: !!rescheduleUid,
name: attendees[0].name,
email: attendees[0].email,
location,
eventName: profile.eventName || "",
bookingId: id,
allRemainingBookings: true,
email: bookingForm.getValues("email"),
eventTypeSlug: eventType.slug,
formerTime: booking?.startTime.toString(),
},
});
},
@@ -265,7 +244,7 @@ const BookingPage = ({
const bookingFormSchema = z
.object({
name: z.string().min(1),
email: z.string().email(),
email: z.string().trim().email(),
phone: z
.string()
.refine((val) => isValidPhoneNumber(val))
@@ -322,6 +301,39 @@ const BookingPage = ({
}
const bookEvent = (booking: BookingFormValues) => {
const bookingCustomInputs = Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && booking.customInputs[inputId] ? booking.customInputs[inputId] : "",
}));
// Checking if custom inputs of type Phone number are valid to display error message on UI
if (eventType.customInputs.length) {
let isErrorFound = false;
eventType.customInputs.forEach((customInput) => {
if (customInput.required && customInput.type === EventTypeCustomInputType.PHONE) {
const input = bookingCustomInputs.find((i) => i.label === customInput.label);
try {
z.string({
errorMap: () => ({
message: `Missing ${customInput.type} customInput: '${customInput.label}'`,
}),
})
.refine((val) => isValidPhoneNumber(val), {
message: "Phone number is invalid",
})
.parse(input?.value);
} catch (err) {
isErrorFound = true;
bookingForm.setError(`customInputs.${customInput.id}`, {
type: "custom",
message: "Invalid Phone number",
});
}
}
});
if (isErrorFound) return;
}
telemetry.event(
top !== window ? telemetryEventTypes.embedBookingConfirmed : telemetryEventTypes.bookingConfirmed,
{ isTeamBooking: document.URL.includes("team/") }
@@ -340,13 +352,29 @@ const BookingPage = ({
{}
);
if (eventType.customInputs.length > 0) {
// find all required custom inputs and ensure they are filled out in the booking form
const requiredCustomInputs = eventType.customInputs.filter((input) => input.required);
const missingRequiredCustomInputs = requiredCustomInputs.filter(
(input) => !booking?.customInputs?.[input.id]
);
if (missingRequiredCustomInputs.length > 0) {
missingRequiredCustomInputs.forEach((input) => {
bookingForm.setError(`customInputs.${input.id}`, {
type: "required",
});
});
return;
}
}
if (recurringDates.length) {
// Identify set of bookings to one intance of recurring event to support batch changes
const recurringEventId = uuidv4();
const recurringBookings = recurringDates.map((recurringDate) => ({
...booking,
start: dayjs(recurringDate).format(),
end: dayjs(recurringDate).add(eventType.length, "minute").format(),
end: dayjs(recurringDate).add(duration, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
recurringEventId,
@@ -362,10 +390,7 @@ const BookingPage = ({
attendeeAddress: booking.attendeeAddress,
}),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
})),
customInputs: bookingCustomInputs,
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
@@ -379,7 +404,7 @@ const BookingPage = ({
mutation.mutate({
...booking,
start: dayjs(date).format(),
end: dayjs(date).add(eventType.length, "minute").format(),
end: dayjs(date).add(duration, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
timeZone: timeZone(),
@@ -393,10 +418,7 @@ const BookingPage = ({
attendeeAddress: booking.attendeeAddress,
}),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
})),
customInputs: bookingCustomInputs,
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
@@ -431,6 +453,7 @@ const BookingPage = ({
}
});
}
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
// Define conditional gates here
@@ -454,7 +477,7 @@ const BookingPage = ({
eventTypeTitle: eventType.title,
profileName: profile.name,
})}{" "}
| Cal.com
| {APP_NAME}
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
@@ -464,7 +487,7 @@ const BookingPage = ({
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "" : "sm:my-24",
"my-0 max-w-3xl "
"my-0 max-w-3xl"
)}>
<div
className={classNames(
@@ -473,95 +496,97 @@ const BookingPage = ({
"dark:border-darkgray-300 rounded-md sm:border"
)}>
<div className="sm:flex">
<div className="sm:dark:border-darkgray-300 dark:text-darkgray-600 flex flex-col px-6 pt-6 pb-0 text-gray-600 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
{stripeAppData.price > 0 && (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="items-start text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiRefreshCw className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
<p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringEventCount,
})}
{showEventTypeDetails && (
<div className="sm:dark:border-darkgray-300 dark:text-darkgray-600 flex flex-col px-6 pt-6 pb-0 text-gray-600 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
{stripeAppData.price > 0 && (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<Icon.FiCreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
</div>
)}
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiCalendar className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
<div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((timeFormatted, key) => (
<p key={key}>{timeFormatted}</p>
))}>
<p className="dark:text-darkgray-600 text-sm">
{t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
)}
</div>
</div>
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
{t("former_time")}
</p>
<p className="line-through ">
<Icon.FiCalendar className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
{!!eventType.seatsPerTimeSlot && (
)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="items-start text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiRefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringEventCount,
})}
</p>
</div>
)}
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiUser
className={`mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4 ${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
}`}
/>
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
} mb-2 font-medium`}>
{booking
? eventType.seatsPerTimeSlot - booking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
</p>
<Icon.FiCalendar className="ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
<div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((timeFormatted, key) => (
<p key={key}>{timeFormatted}</p>
))}>
<p className="dark:text-darkgray-600 text-sm">
+ {t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
)}
</div>
</div>
)}
</BookingDescription>
</div>
<div className="p-6 sm:w-1/2">
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
{t("former_time")}
</p>
<p className="line-through ">
<Icon.FiCalendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
{!!eventType.seatsPerTimeSlot && (
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiUser
className={`ml-[2px] mt-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] ${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
}`}
/>
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
} mb-2 font-medium`}>
{booking
? eventType.seatsPerTimeSlot - booking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
</p>
</div>
)}
</BookingDescription>
</div>
)}
<div className={classNames("p-6", showEventTypeDetails ? "sm:w-1/2" : "w-full")}>
<Form form={bookingForm} handleSubmit={bookEvent}>
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-white">
@@ -598,7 +623,7 @@ const BookingPage = ({
/>
{bookingForm.formState.errors.email && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="mr-2 h-3 w-3" />
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("email_validation_error")}</p>
</div>
)}
@@ -639,8 +664,8 @@ const BookingPage = ({
value={location.type}
defaultChecked={i === 0}
/>
<span className="text-sm ltr:ml-2 rtl:mr-2 dark:text-white">
{locationKeyToString(location)}
<span className="text-sm ltr:ml-2 ltr:mr-2 rtl:ml-2 dark:text-white">
{t(locationKeyToString(location) ?? "")}
</span>
</label>
);
@@ -650,7 +675,7 @@ const BookingPage = ({
)}
</>
{/* TODO: Change name and id ="phone" to something generic */}
{AttendeeInput && (
{AttendeeInput && !disableInput && (
<div className="mb-4">
<label
htmlFor={
@@ -664,7 +689,7 @@ const BookingPage = ({
{selectedLocationType === LocationType.Phone
? t("phone_number")
: selectedLocationType === LocationType.AttendeeInPerson
? t("Address")
? t("address")
: ""}
</label>
<div className="mt-1">
@@ -687,12 +712,11 @@ const BookingPage = ({
: ""
}
required
disabled={disableInput}
/>
</div>
{bookingForm.formState.errors.phone && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="mr-2 h-3 w-3" />
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p>
</div>
)}
@@ -705,8 +729,11 @@ const BookingPage = ({
{input.type !== EventTypeCustomInputType.BOOL && (
<label
htmlFor={"custom_" + input.id}
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{input.label}
className={classNames(
"mb-1 block text-sm font-medium text-gray-700 transition-colors dark:text-white",
bookingForm.formState.errors.customInputs?.[input.id] && "!text-red-700"
)}>
{input.label} {input.required && <span className="text-red-700">*</span>}
</label>
)}
{input.type === EventTypeCustomInputType.TEXTLONG && (
@@ -770,6 +797,49 @@ const BookingPage = ({
</div>
</div>
)}
{input.options && input.type === EventTypeCustomInputType.RADIO && (
<div className="flex">
<Group
name={`customInputs.${input.id}`}
required={input.required}
onValueChange={(e) => {
bookingForm.setValue(`customInputs.${input.id}`, e);
}}>
<>
{input.options.map((option, i) => (
<RadioField
label={option.label}
key={`option.${input.id}.${i}.radio`}
value={option.label}
id={`option.${input.id}.${i}.radio`}
/>
))}
</>
{bookingForm.formState.errors.customInputs?.[input.id] && (
<div className="mt-px flex items-center text-xs text-red-700 ">
<p>{t("required")}</p>
</div>
)}
</Group>
</div>
)}
{input.type === EventTypeCustomInputType.PHONE && (
<div>
<PhoneInput<BookingFormValues>
name={`customInputs.${input.id}`}
control={bookingForm.control}
placeholder={t("enter_phone_number")}
id={`customInputs.${input.id}`}
required={input.required}
/>
{bookingForm.formState.errors?.customInputs?.[input.id] && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p>
</div>
)}
</div>
)}
</div>
))}
{!eventType.disableGuests && guestToggle && (
@@ -844,7 +914,7 @@ const BookingPage = ({
</div>
{bookingForm.formState.errors.smsReminderNumber && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<Icon.FiInfo className="mr-2 h-3 w-3" />
<Icon.FiInfo className="h-3 w-3 ltr:mr-2 rtl:ml-2" />
<p>{t("invalid_number")}</p>
</div>
)}
@@ -868,7 +938,7 @@ const BookingPage = ({
) : (
<textarea
{...bookingForm.register("notes")}
required={!!eventType.metadata.additionalNotesRequired}
required={!!eventType.metadata?.additionalNotesRequired}
id="notes"
name="notes"
rows={3}
@@ -883,7 +953,7 @@ const BookingPage = ({
{!eventType.disableGuests && !guestToggle && (
<Button
type="button"
color="minimalSecondary"
color="minimal"
size="icon"
tooltip={t("additional_guests")}
StartIcon={Icon.FiUserPlus}
@@ -901,7 +971,6 @@ const BookingPage = ({
</Button>
<Button
type="submit"
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast mr-auto dark:border-transparent"
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}
loading={mutation.isLoading || recurringMutation.isLoading}>
{rescheduleUid ? t("reschedule") : t("confirm")}
@@ -934,7 +1003,7 @@ function ErrorMessage({ error }: { error: unknown }) {
<div className="ltr:ml-3 rtl:mr-3">
<p className="text-sm text-yellow-700">
{rescheduleUid ? t("reschedule_fail") : t("booking_fail")}{" "}
{error instanceof HttpError || error instanceof Error ? error.message : "Unknown error"}
{error instanceof HttpError || error instanceof Error ? t(error.message) : "Unknown error"}
</p>
</div>
</div>
@@ -1,74 +0,0 @@
import * as DialogPrimitive from "@radix-ui/react-dialog";
import React, { PropsWithChildren } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/Button";
import { DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
export type DeleteStripeDialogContentProps = {
cancelAllBookingsBtnText?: string;
removeBtnText?: string;
cancelBtnText?: string;
onConfirm?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onRemove?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
title: string;
variety?: "danger" | "warning" | "success";
};
export default function DeleteStripeDialogContent(props: PropsWithChildren<DeleteStripeDialogContentProps>) {
const { t } = useLocale();
const {
title,
variety,
cancelAllBookingsBtnText,
removeBtnText,
cancelBtnText = t("cancel"),
onConfirm,
onRemove,
children,
} = props;
return (
<DialogContent>
<div className="flex">
{variety && (
<div className="mt-0.5 ltr:mr-3">
{variety === "danger" && (
<div className="mx-auto rounded-full bg-red-100 p-2 text-center">
<Icon.FiAlertTriangle className="h-5 w-5 text-red-600" />
</div>
)}
{variety === "warning" && (
<div className="mx-auto rounded-full bg-orange-100 p-2 text-center">
<Icon.FiAlertTriangle className="h-5 w-5 text-orange-600" />
</div>
)}
{variety === "success" && (
<div className="mx-auto rounded-full bg-green-100 p-2 text-center">
<Icon.FiCheck className="h-5 w-5 text-green-600" />
</div>
)}
</div>
)}
<div>
<DialogPrimitive.Title className="font-cal text-xl text-gray-900">{title}</DialogPrimitive.Title>
<DialogPrimitive.Description className="text-sm text-neutral-500">
{children}
</DialogPrimitive.Description>
</div>
</div>
<div className="mt-5 flex flex-row-reverse gap-x-2 sm:mt-8">
<DialogClose onClick={onConfirm} asChild>
<Button color="alert">{cancelAllBookingsBtnText}</Button>
</DialogClose>
<DialogClose onClick={onRemove} asChild>
<Button color="alert2">{removeBtnText}</Button>
</DialogClose>
<DialogClose asChild>
<Button color="secondary">{cancelBtnText}</Button>
</DialogClose>
</div>
</DialogContent>
);
}
+159 -125
View File
@@ -3,30 +3,28 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { components } from "react-select";
import { z } from "zod";
import {
LocationType,
getEventLocationType,
EventLocationType,
getEventLocationType,
getHumanReadableLocationValue,
getMessageForOrganizer,
LocationObject,
LocationType,
} from "@calcom/app-store/locations";
import { getMessageForOrganizer } from "@calcom/app-store/locations";
import { getHumanReadableLocationValue } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
import { Form } from "@calcom/ui/form/fields";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Icon, PhoneInput } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
import CheckboxField from "@components/ui/form/CheckboxField";
import Select from "@components/ui/form/Select";
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number];
type OptionTypeBase = {
label: string;
@@ -42,6 +40,7 @@ interface ISetLocationDialog {
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean;
setSelectedLocation?: (param: OptionTypeBase | undefined) => void;
setEditingLocationType?: (param: string) => void;
}
const LocationInput = (props: {
@@ -79,9 +78,10 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
isOpenDialog,
defaultValues,
setSelectedLocation,
setEditingLocationType,
} = props;
const { t } = useLocale();
const locationsQuery = trpc.useQuery(["viewer.locationOptions"]);
const locationsQuery = trpc.viewer.locationOptions.useQuery();
useEffect(() => {
if (selection) {
@@ -212,13 +212,13 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
return (
<Dialog open={isOpenDialog}>
<DialogContent asChild>
<div className="inline-block transform rounded-sm bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<DialogContent>
<div className="flex flex-row space-x-3">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
</div>
<div className="w-full">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{t("edit_location")}
</h3>
@@ -226,118 +226,152 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
<p className="text-sm text-gray-400">{t("this_input_will_shown_booking_this_event")}</p>
)}
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" />
</div>
{booking && (
<>
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
<p className="mb-2 ml-1 text-sm text-black">
{getHumanReadableLocationValue(booking.location, t)}
</p>
</>
)}
<Form
form={locationFormMethods}
handleSubmit={async (values) => {
const { locationType: newLocation, displayLocationPublicly } = values;
<div className="mt-3 text-center sm:mt-0 sm:text-left" />
let details = {};
if (newLocation === LocationType.InPerson) {
details = {
address: values.locationAddress,
};
}
const eventLocationType = getEventLocationType(newLocation);
{booking && (
<>
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
<p className="mb-2 ml-1 text-sm text-black">
{getHumanReadableLocationValue(booking.location, t)}
</p>
</>
)}
<Form
form={locationFormMethods}
handleSubmit={async (values) => {
const { locationType: newLocation, displayLocationPublicly } = values;
// TODO: There can be a property that tells if it is to be saved in `link`
if (
newLocation === LocationType.Link ||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
) {
details = { link: values.locationLink };
}
let details = {};
if (newLocation === LocationType.InPerson) {
details = {
address: values.locationAddress,
};
}
const eventLocationType = getEventLocationType(newLocation);
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
// TODO: There can be a property that tells if it is to be saved in `link`
if (
newLocation === LocationType.Link ||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
) {
details = { link: values.locationLink };
}
if (eventLocationType?.organizerInputType) {
details = {
...details,
displayLocationPublicly,
};
}
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister([
"locationType",
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
}}>
<QueryCell
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select
maxMenuHeight={150}
name="location"
defaultValue={selection}
options={
booking
? locationOptions.filter(
(location) =>
location.value !== "phone" && location.value !== "attendeeInPerson"
)
: locationOptions
}
isSearchable
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)}
/>
);
}}
/>
{selectedLocation && LocationOptions}
<div className="mt-4 flex justify-end space-x-2">
<Button
onClick={() => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister("locationType");
if (eventLocationType?.organizerInputType) {
details = {
...details,
displayLocationPublicly,
};
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister([
"locationType",
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
}}>
<QueryCell
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
if (booking) {
locationOptions.forEach((location) => {
if (location.label === "phone") {
location.options.filter((l) => l.value !== "phone");
} else if (location.label === "in person") {
location.options.filter((l) => l.value !== "attendeeInPerson");
}
});
}
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select<{ label: string; value: string; icon?: string }>
maxMenuHeight={300}
name="location"
defaultValue={selection}
options={locationOptions}
components={{
Option: (props) => (
<components.Option {...props}>
<div className="flex items-center gap-3">
{props.data.icon && (
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
)}
<span
className={classNames(
"text-sm font-medium",
props.isSelected ? "text-white" : "text-gray-900"
)}>
{props.data.label}
</span>
</div>
</components.Option>
),
}}
formatOptionLabel={(e) => (
<div className="flex items-center gap-3">
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
<span>{e.label}</span>
</div>
)}
formatGroupLabel={(e) => (
<p className="text-xs font-medium text-gray-600">{e.label}</p>
)}
isSearchable
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)}
/>
);
}}
type="button"
color="secondary">
{t("cancel")}
</Button>
<Button type="submit">{t("update")}</Button>
</div>
</Form>
/>
{selectedLocation && LocationOptions}
<DialogFooter>
<div className="mt-4 flex justify-end space-x-2 rtl:space-x-reverse">
<Button
onClick={() => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
setEditingLocationType?.("");
locationFormMethods.unregister("locationType");
}}
type="button"
color="secondary">
{t("cancel")}
</Button>
<Button type="submit">{t("update")}</Button>
</div>
</DialogFooter>
</Form>
</div>
</div>
</DialogContent>
</Dialog>
+16 -13
View File
@@ -1,12 +1,18 @@
import React, { useState, Dispatch, SetStateAction } from "react";
import { Dispatch, SetStateAction, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components/button";
import { TextArea } from "@calcom/ui/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
Icon,
showToast,
TextArea,
} from "@calcom/ui";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -20,11 +26,11 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
const [rescheduleReason, setRescheduleReason] = useState("");
const { mutate: rescheduleApi, isLoading } = trpc.useMutation("viewer.bookings.requestReschedule", {
const { mutate: rescheduleApi, isLoading } = trpc.viewer.bookings.requestReschedule.useMutation({
async onSuccess() {
showToast(t("reschedule_request_sent"), "success");
setIsOpenDialog(false);
await utils.invalidateQueries(["viewer.bookings"]);
await utils.viewer.bookings.invalidate();
},
onError() {
showToast(t("unexpected_error_try_again"), "error");
@@ -41,8 +47,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
</div>
<div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} />
<p className="-mt-8 text-sm text-gray-500">{t("reschedule_modal_description")}</p>
<p className="text-sm text-gray-500">{t("reschedule_modal_description")}</p>
<p className="mt-6 mb-2 text-sm font-bold text-black">
{t("reason_for_reschedule_request")}
<span className="font-normal text-gray-500"> (Optional)</span>
@@ -56,9 +61,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
/>
<DialogFooter>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<DialogClose />
<Button
data-testid="send_request"
disabled={isLoading}
@@ -1,6 +1,6 @@
import { FormValues } from "pages/event-types/[type]";
import { Controller, useFormContext } from "react-hook-form";
import { SingleValueProps, OptionProps, components } from "react-select";
import { components, OptionProps, SingleValueProps } from "react-select";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
@@ -8,12 +8,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Icon } from "@calcom/ui";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { SettingsToggle } from "@calcom/ui/v2";
import Select from "@calcom/ui/v2/core/form/select";
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
import { Badge, Button, Icon, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
@@ -25,12 +20,13 @@ type AvailabilityOption = {
const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.Option {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.Option>
@@ -39,12 +35,13 @@ const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const SingleValue = ({ ...props }: SingleValueProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.SingleValue {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.SingleValue>
@@ -61,7 +58,9 @@ const AvailabilitySelect = ({
onBlur: () => void;
onChange: (value: AvailabilityOption | null) => void;
}) => {
const { data, isLoading } = trpc.useQuery(["viewer.availability.list"]);
const { data, isLoading } = trpc.viewer.availability.list.useQuery();
const { t } = useLocale();
if (isLoading) {
return <SelectSkeletonLoader />;
}
@@ -82,6 +81,7 @@ const AvailabilitySelect = ({
return (
<Select
placeholder={t("select")}
options={options}
isSearchable={false}
onChange={props.onChange}
@@ -149,7 +149,7 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
<span className="w-16 sm:w-28 sm:text-left">
{format(dayRange.startTime, timeFormat === 12)}
</span>
<span className="ml-4">-</span>
<span className="ltr:ml-4 rtl:mr-4">-</span>
<div className="ml-6">{format(dayRange.endTime, timeFormat === 12)}</div>
</div>
))}
@@ -164,7 +164,7 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
<hr />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
<span className="flex items-center justify-center text-sm text-gray-600 sm:justify-start">
<Icon.FiGlobe className="mr-2" />
<Icon.FiGlobe className="ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
<Button
@@ -183,7 +183,7 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
};
const scheduleId = watch("schedule");
const { isLoading, data: schedule } = trpc.useQuery(["viewer.availability.schedule", { scheduleId }]);
const { isLoading, data: schedule } = trpc.viewer.availability.schedule.get.useQuery({ scheduleId });
const filterDays = (dayNum: number) =>
schedule?.schedule.availability.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
@@ -1,349 +0,0 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { SchedulingType } from "@prisma/client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import type { z } from "zod";
import classNames from "@calcom/lib/classNames";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/Alert";
import { Button } from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import Dropdown, {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@calcom/ui/Dropdown";
import { Icon } from "@calcom/ui/Icon";
import { Form, InputLeading, TextAreaField, TextField } from "@calcom/ui/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
import { HttpError } from "@lib/core/http/error";
import { slugify } from "@lib/slugify";
import Avatar from "@components/ui/Avatar";
import * as RadioArea from "@components/ui/form/radio-area";
// this describes the uniform data needed to create a new event type on Profile or Team
export interface EventTypeParent {
teamId: number | null | undefined; // if undefined, then it's a profile
name?: string | null;
slug?: string | null;
image?: string | null;
}
interface CreateEventTypeBtnProps {
// set true for use on the team settings page
canAddEvents: boolean;
// set true when in use on the team settings page
isIndividualTeam?: boolean;
// EventTypeParent can be a profile (as first option) or a team for the rest.
options: EventTypeParent[];
}
export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
const { t, isLocaleReady } = useLocale();
const router = useRouter();
// URL encoded params
const teamId: number | undefined =
typeof router.query.teamId === "string" && router.query.teamId
? parseInt(router.query.teamId)
: undefined;
const pageSlug = router.query.eventPage || props.options[0].slug;
const hasTeams = !!props.options.find((option) => option.teamId);
const type: string = typeof router.query.type == "string" && router.query.type ? router.query.type : "";
const form = useForm<z.infer<typeof createEventTypeInput>>({
resolver: zodResolver(createEventTypeInput),
});
const { setValue, watch, register } = form;
useEffect(() => {
if (!router.isReady) return;
const title: string =
typeof router.query.title === "string" && router.query.title ? router.query.title : "";
const length: number =
typeof router.query.length === "string" && router.query.length ? parseInt(router.query.length) : 15;
const description: string =
typeof router.query.description === "string" && router.query.description
? router.query.description
: "";
const slug: string = typeof router.query.slug === "string" && router.query.slug ? router.query.slug : "";
setValue("title", title);
setValue("length", length);
setValue("description", description);
setValue("slug", slug);
// If query params change, update the form
}, [router.isReady, router.query, setValue]);
useEffect(() => {
const subscription = watch((value, { name, type }) => {
if (name === "title" && type === "change") {
if (value.title) setValue("slug", slugify(value.title));
else setValue("slug", "");
}
});
return () => subscription.unsubscribe();
}, [watch, setValue]);
const createMutation = trpc.useMutation("viewer.eventTypes.create", {
onSuccess: async ({ eventType }) => {
await router.replace("/event-types/" + eventType.id);
showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success");
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
if (err.data?.code === "BAD_REQUEST") {
const message = `${err.data.code}: URL already exists.`;
showToast(message, "error");
}
if (err.data?.code === "UNAUTHORIZED") {
const message = `${err.data.code}: You are not able to create this event`;
showToast(message, "error");
}
},
});
// inject selection data into url for correct router history
const openModal = (option: EventTypeParent) => {
const query = {
...router.query,
dialog: "new-eventtype",
eventPage: option.slug,
teamId: option.teamId,
};
if (!option.teamId) {
delete query.teamId;
}
router.push(
{
pathname: router.pathname,
query,
},
undefined,
{ shallow: true }
);
};
if (!isLocaleReady) {
return null;
}
return (
<Dialog
name="new-eventtype"
clearQueryParamsOnClose={["eventPage", "teamId", "type", "description", "title", "length", "slug"]}>
<CreateEventTypeTrigger
hasTeams={hasTeams}
canAddEvents={props.canAddEvents}
isIndividualTeam={props.isIndividualTeam}
openModal={openModal}
options={props.options}
/>
<DialogContent>
<div className="mb-4">
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
{teamId ? t("add_new_team_event_type") : t("add_new_event_type")}
</h3>
<div>
<p className="text-sm text-gray-500">{t("new_event_type_to_book_description")}</p>
</div>
</div>
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-4">
{teamId && (
<TextField
type="hidden"
labelProps={{ style: { display: "none" } }}
{...register("teamId", { valueAsNumber: true })}
value={teamId}
/>
)}
<TextField label={t("title")} placeholder={t("quick_chat")} {...register("title")} />
{process.env.NEXT_PUBLIC_WEBSITE_URL !== undefined &&
process.env.NEXT_PUBLIC_WEBSITE_URL?.length >= 21 ? (
<TextField
label={`${t("url")}: ${process.env.NEXT_PUBLIC_WEBSITE_URL}`}
required
addOnLeading={<InputLeading>/{pageSlug}/</InputLeading>}
{...register("slug")}
/>
) : (
<TextField
label={t("url")}
required
addOnLeading={
<InputLeading>
{process.env.NEXT_PUBLIC_WEBSITE_URL}/{pageSlug}/
</InputLeading>
}
{...register("slug")}
/>
)}
<TextAreaField
label={t("description")}
placeholder={t("quick_video_meeting")}
{...register("description")}
/>
<div className="relative">
<TextField
type="number"
required
min="10"
placeholder="15"
label={t("length")}
className="pr-20"
{...register("length", { valueAsNumber: true })}
/>
<div className="absolute inset-y-0 right-0 mt-1.5 flex items-center pt-4 pr-3 text-sm text-gray-400">
{t("minutes")}
</div>
</div>
{teamId && (
<div className="mb-4">
<label htmlFor="schedulingType" className="block text-sm font-bold text-gray-700">
{t("scheduling_type")}
</label>
{form.formState.errors.schedulingType && (
<Alert
className="mt-1"
severity="error"
message={form.formState.errors.schedulingType.message}
/>
)}
<RadioArea.Group
{...register("schedulingType")}
onChange={(val) => form.setValue("schedulingType", val as SchedulingType)}
className="relative mt-1 flex space-x-6 rounded-sm rtl:space-x-reverse">
<RadioArea.Item
value={SchedulingType.COLLECTIVE}
defaultChecked={type === SchedulingType.COLLECTIVE}
className="w-1/2 text-sm">
<strong className="mb-1 block">{t("collective")}</strong>
<p>{t("collective_description")}</p>
</RadioArea.Item>
<RadioArea.Item
value={SchedulingType.ROUND_ROBIN}
defaultChecked={type === SchedulingType.ROUND_ROBIN}
className="w-1/2 text-sm">
<strong className="mb-1 block">{t("round_robin")}</strong>
<p>{t("round_robin_description")}</p>
</RadioArea.Item>
</RadioArea.Group>
</div>
)}
</div>
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button type="submit" loading={createMutation.isLoading}>
{t("continue")}
</Button>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
</div>
</Form>
</DialogContent>
</Dialog>
);
}
type CreateEventTypeTrigger = {
isIndividualTeam?: boolean;
// EventTypeParent can be a profile (as first option) or a team for the rest.
options: EventTypeParent[];
hasTeams: boolean;
// set true for use on the team settings page
canAddEvents: boolean;
openModal: (option: EventTypeParent) => void;
};
export function CreateEventTypeTrigger(props: CreateEventTypeTrigger) {
const { t } = useLocale();
return (
<>
{!props.hasTeams || props.isIndividualTeam ? (
<Button
onClick={() => props.openModal(props.options[0])}
data-testid="new-event-type"
StartIcon={Icon.FiPlus}
disabled={!props.canAddEvents}>
{t("new_event_type_btn")}
</Button>
) : (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button EndIcon={Icon.FiChevronDown}>{t("new_event_type_btn")}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{t("new_event_subtitle")}</DropdownMenuLabel>
<DropdownMenuSeparator className="h-px bg-gray-200" />
{props.options.map((option) => (
<CreateEventTeamsItem
key={option.slug}
option={option}
openModal={() => props.openModal(option)}
/>
))}
</DropdownMenuContent>
</Dropdown>
)}
</>
);
}
function CreateEventTeamsItem(props: {
openModal: (option: EventTypeParent) => void;
option: EventTypeParent;
}) {
const session = useSession();
const membershipQuery = trpc.useQuery([
"viewer.teams.getMembershipbyUser",
{
memberId: session.data?.user.id as number,
teamId: props.option.teamId as number,
},
]);
const isDisabled = membershipQuery.data?.role === "MEMBER";
return (
<DropdownMenuItem
key={props.option.slug}
className={classNames(
"cursor-pointer px-3 py-2 focus:outline-none",
isDisabled ? "cursor-default !text-gray-300" : "hover:bg-neutral-100"
)}
disabled={isDisabled}
onSelect={() => props.openModal(props.option)}>
<Avatar
alt={props.option.name || ""}
imageSrc={props.option.image || WEBAPP_URL + "/" + props.option.slug + "/avatar.png"}
size={6}
className="inline ltr:mr-2 rtl:ml-2"
/>
{props.option.name ? props.option.name : props.option.slug}
</DropdownMenuItem>
);
}
@@ -0,0 +1,212 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventTypeCustomInputType } from "@prisma/client";
import type { CustomInputParsed } from "pages/event-types/[type]";
import { FC } from "react";
import { Control, Controller, useFieldArray, useForm, UseFormRegister, useWatch } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, TextField } from "@calcom/ui";
interface OptionTypeBase {
label: string;
value: EventTypeCustomInputType;
options?: { label: string; type: string }[];
}
interface Props {
onSubmit: (output: CustomInputParsed) => void;
onCancel: () => void;
selectedCustomInput?: CustomInputParsed;
}
type IFormInput = CustomInputParsed;
/**
* Getting a random ID gives us the option to know WHICH field is changed
* when the user edits a custom field.
* This UUID is only used to check for changes in the UI and not the ID we use in the DB
* There is very very very slim chance that this will cause a collision
* */
const randomId = () => Math.floor(Math.random() * 1000000 + new Date().getTime());
const CustomInputTypeForm: FC<Props> = (props) => {
const { t } = useLocale();
const inputOptions: OptionTypeBase[] = [
{ value: EventTypeCustomInputType.TEXT, label: t("text") },
{ value: EventTypeCustomInputType.TEXTLONG, label: t("multiline_text") },
{ value: EventTypeCustomInputType.NUMBER, label: t("number") },
{ value: EventTypeCustomInputType.BOOL, label: t("checkbox") },
{
value: EventTypeCustomInputType.RADIO,
label: t("radio"),
},
{ value: EventTypeCustomInputType.PHONE, label: t("phone_number") },
];
const { selectedCustomInput } = props;
const defaultValues = selectedCustomInput
? { ...selectedCustomInput, id: selectedCustomInput?.id || randomId() }
: {
id: randomId(),
type: EventTypeCustomInputType.TEXT,
};
const { register, control, getValues } = useForm<IFormInput>({
defaultValues,
});
const selectedInputType = useWatch({ name: "type", control });
const selectedInputOption = inputOptions.find((e) => selectedInputType === e.value);
const onCancel = () => {
props.onCancel();
};
return (
<div className="flex flex-col space-y-4">
<div>
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
{t("input_type")}
</label>
<Controller
name="type"
control={control}
render={({ field }) => (
<Select
id="type"
defaultValue={selectedInputOption}
options={inputOptions}
isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 text-sm"
onChange={(option) => option && field.onChange(option.value)}
value={selectedInputOption}
onBlur={field.onBlur}
name={field.name}
/>
)}
/>
</div>
<TextField
label={t("label")}
type="text"
id="label"
required
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={selectedCustomInput?.label}
{...register("label", { required: true })}
/>
{(selectedInputType === EventTypeCustomInputType.TEXT ||
selectedInputType === EventTypeCustomInputType.TEXTLONG) && (
<TextField
label={t("placeholder")}
type="text"
id="placeholder"
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={selectedCustomInput?.placeholder}
{...register("placeholder")}
/>
)}
{selectedInputType === EventTypeCustomInputType.RADIO && (
<RadioInputHandler control={control} register={register} />
)}
<div className="flex h-5 items-center">
<input
id="required"
type="checkbox"
className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-gray-300 ltr:mr-2 rtl:ml-2"
defaultChecked={selectedCustomInput?.required ?? true}
{...register("required")}
/>
<label htmlFor="required" className="block text-sm font-medium text-gray-700">
{t("is_required")}
</label>
</div>
<input
type="hidden"
id="eventTypeId"
value={selectedCustomInput?.eventTypeId || -1}
{...register("eventTypeId", { valueAsNumber: true })}
/>
<input
type="hidden"
id="id"
value={selectedCustomInput?.id || -1}
{...register("id", { valueAsNumber: true })}
/>
<div className="mt-5 flex justify-end space-x-2 rtl:space-x-reverse sm:mt-4">
<Button onClick={onCancel} type="button" color="secondary" className="ltr:mr-2 rtl:ml-2">
{t("cancel")}
</Button>
<Button
type="button"
onClick={() => {
props.onSubmit(getValues());
}}>
{t("save")}
</Button>
</div>
</div>
);
};
function RadioInputHandler({
register,
control,
}: {
register: UseFormRegister<IFormInput>;
control: Control<IFormInput>;
}) {
const { t } = useLocale();
const { fields, append, remove } = useFieldArray<IFormInput>({
control,
name: "options",
shouldUnregister: true,
});
const [animateRef] = useAutoAnimate<HTMLUListElement>();
return (
<div className="flex flex-col ">
<Label htmlFor="radio_options">{t("options")}</Label>
<ul
className="flex max-h-80 w-full flex-col space-y-1 overflow-y-scroll rounded-md bg-gray-50 p-4"
ref={animateRef}>
<>
{fields.map((option, index) => (
<li key={`${option.id}`}>
<TextField
id={option.id}
placeholder={t("enter_option", { index: index + 1 })}
addOnFilled={false}
label={t("option", { index: index + 1 })}
labelSrOnly
{...register(`options.${index}.label` as const, { required: true })}
addOnSuffix={
<Button
size="icon"
color="minimal"
StartIcon={Icon.FiX}
onClick={() => {
remove(index);
}}
/>
}
/>
</li>
))}
<Button
color="minimal"
StartIcon={Icon.FiPlus}
className="!text-sm !font-medium"
onClick={() => {
append({ label: "", type: "text" });
}}>
{t("add_an_option")}
</Button>
</>
</ul>
</div>
);
}
export default CustomInputTypeForm;
@@ -1,20 +1,34 @@
import { EventTypeCustomInput } from "@prisma/client/";
import Link from "next/link";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import type { CustomInputParsed, EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { CAL_URL } from "@calcom/lib/constants";
import CustomInputItem from "@calcom/features/eventtypes/components/CustomInputItem";
import { APP_NAME, CAL_URL, IS_SELF_HOSTED } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Checkbox, Button, TextField, Label } from "@calcom/ui/components";
import { CustomInputItem, Dialog, DialogContent, SettingsToggle, showToast, Tooltip } from "@calcom/ui/v2";
import {
Badge,
Button,
Checkbox,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
Icon,
Label,
SettingsToggle,
showToast,
TextField,
Tooltip,
} from "@calcom/ui";
import CustomInputTypeForm from "@components/v2/eventtype/CustomInputTypeForm";
import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm";
import RequiresConfirmationController from "./RequiresConfirmationController";
const generateHashedLink = (id: number) => {
const translator = short();
@@ -23,21 +37,33 @@ const generateHashedLink = (id: number) => {
return uid;
};
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered, "eventType" | "team">) => {
const connectedCalendarsQuery = trpc.useQuery(["viewer.connectedCalendars"]);
const getRandomId = (length = 8) => {
return (
-1 *
parseInt(
Math.ceil(Math.random() * Date.now())
.toPrecision(length)
.toString()
.replace(".", "")
)
);
};
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const [customInputs, setCustomInputs] = useState<EventTypeCustomInput[]>(
const [customInputs, setCustomInputs] = useState<CustomInputParsed[]>(
eventType.customInputs.sort((a, b) => a.id - b.id) || []
);
const [selectedCustomInput, setSelectedCustomInput] = useState<EventTypeCustomInput | undefined>(undefined);
const [selectedCustomInput, setSelectedCustomInput] = useState<CustomInputParsed | undefined>(undefined);
const [selectedCustomInputModalOpen, setSelectedCustomInputModalOpen] = useState(false);
const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
const seatsEnabled = formMethods.getValues("seatsPerTimeSlotEnabled");
const removeCustom = (index: number) => {
@@ -50,6 +76,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
!hashedUrl && setHashedUrl(generateHashedLink(eventType.users[0]?.id ?? team?.id));
}, [eventType.users, hashedUrl, team?.id]);
useEffect(() => {
if (eventType.customInputs) {
setCustomInputs(eventType.customInputs.sort((a, b) => a.id - b.id));
}
}, [eventType.customInputs]);
return (
<div className="flex flex-col space-y-8">
{/**
@@ -61,10 +93,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<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">
@@ -74,6 +107,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
defaultValue={eventType.destinationCalendar || undefined}
render={({ field: { onChange, value } }) => (
<DestinationCalendarSelector
destinationCalendar={eventType.destinationCalendar}
value={value ? value.externalId : undefined}
onChange={onChange}
hidePlaceholder
@@ -88,7 +122,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
addOnSuffix={
@@ -97,7 +131,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
StartIcon={Icon.FiEdit}
size="icon"
color="minimal"
className="hover:stroke-3 hover:bg-transparent hover:text-black"
className="hover:stroke-3 min-w-fit px-0 hover:bg-transparent hover:text-black"
onClick={() => setShowEventNameTip((old) => !old)}
/>
}
@@ -112,15 +146,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
onCheckedChange={(e) => {
if (e && customInputs.length === 0) {
// Push a placeholders
setSelectedCustomInput(undefined);
setSelectedCustomInputModalOpen(true);
} else if (!e) {
setCustomInputs([]);
formMethods.setValue("customInputs", []);
}
}}>
<ul className="my-4">
{customInputs.map((customInput: EventTypeCustomInput, idx: number) => (
<ul className="my-4 rounded-md border">
{customInputs.map((customInput, idx) => (
<CustomInputItem
key={idx}
question={customInput.label}
@@ -143,24 +175,17 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
setSelectedCustomInput(undefined);
setSelectedCustomInputModalOpen(true);
}}>
Add an input
{t("add_input")}
</Button>
)}
</SettingsToggle>
</div>
<hr />
<Controller
name="requiresConfirmation"
defaultValue={eventType.requiresConfirmation}
render={({ field: { value, onChange } }) => (
<SettingsToggle
title={t("requires_confirmation")}
description={t("requires_confirmation_description")}
checked={value}
onCheckedChange={(e) => onChange(e)}
disabled={seatsEnabled}
/>
)}
<RequiresConfirmationController
seatsEnabled={seatsEnabled}
metadata={eventType.metadata}
requiresConfirmation={requiresConfirmation}
onRequiresConfirmation={setRequiresConfirmation}
/>
<hr />
<Controller
@@ -233,17 +258,32 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
defaultValue={eventType.successRedirectUrl || ""}
{...formMethods.register("successRedirectUrl")}
/>
<div className="mt-2 flex">
<Checkbox
description={t("disable_success_page")}
// Disable if it's not Self Hosted or if the redirect url is not set
disabled={!IS_SELF_HOSTED || !formMethods.watch("successRedirectUrl")}
{...formMethods.register("metadata.disableSuccessPage")}
/>
{/*TODO: Extract it out into a component when used more than once*/}
{!IS_SELF_HOSTED && (
<Link href="https://cal.com/pricing" target="_blank">
<Badge variant="orange" className="ml-2">
Platform Only
</Badge>
</Link>
)}
</div>
</div>
</SettingsToggle>
</>
)}
/>
<hr />
<SettingsToggle
data-testid="hashedLinkCheck"
title={t("private_link")}
description={t("private_link_description")}
description={t("private_link_description", { appName: APP_NAME })}
checked={hashedLinkVisible}
onCheckedChange={(e) => {
formMethods.setValue("hashedLink", e ? hashedUrl : undefined);
@@ -296,6 +336,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
if (e) {
formMethods.setValue("disableGuests", true);
formMethods.setValue("requiresConfirmation", false);
setRequiresConfirmation(false);
formMethods.setValue("seatsPerTimeSlot", 2);
} else {
formMethods.setValue("seatsPerTimeSlot", null);
@@ -341,15 +382,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<DialogContent
title={t("custom_event_name")}
description={t("custom_event_name_description")}
type="creation"
actionText="Create"
// Set event name back to what it was on close
actionOnClose={() => formMethods.setValue("eventName", eventType.eventName ?? "")}
actionOnClick={() => setShowEventNameTip(false)}>
type="creation">
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
/>
@@ -359,42 +396,61 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<p>{`{HOST/ATTENDEE} = ${t("dynamically_display_attendee_or_organizer")}`}</p>
<p>{`{LOCATION} = ${t("event_location")}`}</p>
</div>
<DialogFooter>
<Button color="primary" onClick={() => setShowEventNameTip(false)}>
{t("create")}
</Button>
<DialogClose onClick={() => formMethods.setValue("eventName", eventType.eventName ?? "")} />
</DialogFooter>
</DialogContent>
</Dialog>
)}
<Controller
name="customInputs"
control={formMethods.control}
defaultValue={eventType.customInputs.sort((a, b) => a.id - b.id) || []}
defaultValue={customInputs}
render={() => (
<Dialog open={selectedCustomInputModalOpen} onOpenChange={setSelectedCustomInputModalOpen}>
<DialogContent
type="creation"
Icon={Icon.FiPlus}
title={t("add_new_custom_input_field")}
useOwnActionButtons
description={t("this_input_will_shown_booking_this_event")}>
<CustomInputTypeForm
selectedCustomInput={selectedCustomInput}
onSubmit={(values) => {
const customInput: EventTypeCustomInput = {
id: -1,
const customInput: CustomInputParsed = {
id: getRandomId(),
eventTypeId: -1,
label: values.label,
placeholder: values.placeholder,
required: values.required,
type: values.type,
options: values.options,
hasToBeCreated: true,
};
if (selectedCustomInput) {
selectedCustomInput.label = customInput.label;
selectedCustomInput.placeholder = customInput.placeholder;
selectedCustomInput.required = customInput.required;
selectedCustomInput.type = customInput.type;
selectedCustomInput.options = customInput.options || undefined;
selectedCustomInput.hasToBeCreated = false;
// Update by id
const inputIndex = customInputs.findIndex((input) => input.id === values.id);
customInputs[inputIndex] = selectedCustomInput;
setCustomInputs(customInputs);
formMethods.setValue("customInputs", customInputs);
} else {
setCustomInputs(customInputs.concat(customInput));
formMethods.setValue("customInputs", customInputs.concat(customInput));
const concatted = customInputs.concat({
...customInput,
options: customInput.options,
});
console.log(concatted);
setCustomInputs(concatted);
formMethods.setValue("customInputs", concatted);
}
setSelectedCustomInputModalOpen(false);
}}
onCancel={() => {
@@ -1,5 +1,4 @@
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import React from "react";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useFormContext } from "react-hook-form";
import EventTypeAppContext, { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
@@ -7,13 +6,10 @@ import { EventTypeAddonMap } from "@calcom/app-store/apps.browser.generated";
import { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
import { EventTypeAppsList } from "@calcom/app-store/utils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
import { Button } from "@calcom/ui/components";
import { EmptyScreen } from "@calcom/ui/v2";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, ErrorBoundary, Icon } from "@calcom/ui";
type EventType = Pick<EventTypeSetupInfered, "eventType">["eventType"] &
type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
EventTypeAppCardComponentProps["eventType"];
function AppCardWrapper({
@@ -22,7 +18,7 @@ function AppCardWrapper({
getAppData,
setAppData,
}: {
app: inferQueryOutput<"viewer.apps">[number];
app: RouterOutputs["viewer"]["apps"][number];
eventType: EventType;
getAppData: GetAppData;
setAppData: SetAppData;
@@ -44,12 +40,9 @@ function AppCardWrapper({
export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
const { t } = useLocale();
const { data: eventTypeApps, isLoading } = trpc.useQuery([
"viewer.apps",
{
extendsFeature: "EventType",
},
]);
const { data: eventTypeApps, isLoading } = trpc.viewer.apps.useQuery({
extendsFeature: "EventType",
});
const methods = useFormContext<FormValues>();
const installedApps = eventTypeApps?.filter((app) => app.credentials.length);
const notInstalledApps = eventTypeApps?.filter((app) => !app.credentials.length);
@@ -1,23 +1,25 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { useFormContext, Controller, useWatch } from "react-hook-form";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo, useRef, useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { classNames } from "@calcom/lib";
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PeriodType } from "@calcom/prisma/client";
import type { BookingLimit } from "@calcom/types/Calendar";
import { Icon } from "@calcom/ui";
import { Button } from "@calcom/ui/components";
import { Label, Input, MinutesField } from "@calcom/ui/components/form";
import { Select, SettingsToggle } from "@calcom/ui/v2";
import DateRangePicker from "@calcom/ui/v2/core/form/date-range-picker/DateRangePicker";
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">) => {
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
const { t } = useLocale();
const formMethods = useFormContext<FormValues>();
const { eventType } = props;
const minimumBookingNoticeType = useRef<DurationType>(findDurationType(eventType.minimumBookingNotice));
const prevBookingNoticeType = useRef<DurationType>(minimumBookingNoticeType.current);
const minimumBookingNoticeInDurationTypeFormValue = formMethods.watch("minimumBookingNoticeInDurationType");
const PERIOD_TYPES = [
{
type: "ROLLING" as const,
@@ -47,6 +49,57 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
defaultValue: periodType?.type,
});
const onMinimumNoticeDurationTypeChange = useMemo(
() => (durationType?: DurationType) => {
if (typeof durationType === "undefined") return;
// Store current selected type in ref to use in previous run for comparrison.
minimumBookingNoticeType.current = durationType;
// Uses the previous selected type and converts the value to the new type.
// Eg day was selected before, user selects hours now, so we multiple * 24 and round up.
const minimumBookingNoticeInDurationType = convertToNewDurationType(
prevBookingNoticeType.current,
durationType,
Number(minimumBookingNoticeInDurationTypeFormValue)
);
// Uses the round up value in the new type to calculate the value in minutes.
// We should NOT use minimumBookingNoticeInDurationTypeFormValue here, since that
// would break in the case that the user enters 90 minutes, changes the type to
// hours (that will show 2 hours), but the value in minutes would then remain 90,
// which is something else than the user things they save.
const minimumBookingNoticeInMinutes = convertToNewDurationType(
durationType,
"minutes",
Number(minimumBookingNoticeInDurationType)
);
// Updates both form values as well as stores the current type as previous type in ref.
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
formMethods.setValue("minimumBookingNoticeInDurationType", minimumBookingNoticeInDurationType);
prevBookingNoticeType.current = durationType;
},
[formMethods, minimumBookingNoticeInDurationTypeFormValue]
);
/**
* When the user inputs a new value for minimumBookingNoticeInDurationType,
* we calculate the value in minutes, and update this hidden field as well.
*/
const onMinimumNoticeChange = useMemo(
() => (duration: string) => {
const minimumBookingNoticeInMinutes = convertToNewDurationType(
minimumBookingNoticeType.current,
"minutes",
Number(duration)
);
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
},
[formMethods]
);
return (
<div>
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
@@ -116,13 +169,54 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
</div>
</div>
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="w-full">
<MinutesField
required
label={t("minimum_booking_notice")}
type="number"
placeholder="120"
{...formMethods.register("minimumBookingNotice", { valueAsNumber: true })}
<div className="flex w-full items-end justify-end">
<Controller
name="minimumBookingNotice"
control={formMethods.control}
render={() => {
const durationTypeOptions = [
{
label: t("minutes"),
value: "minutes",
},
{
label: t("hours"),
value: "hours",
},
{
label: t("days"),
value: "days",
},
];
return (
<>
<div className="w-1/2 md:w-3/4">
<InputField
required
label={t("minimum_booking_notice")}
type="number"
placeholder="120"
className="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>
<div className="w-full">
@@ -136,7 +230,7 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
label: t("slot_interval_default"),
value: -1,
},
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
@@ -191,7 +285,7 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
<SettingsToggle
title={t("limit_future_bookings")}
description={t("limit_future_bookings_description")}
checked={value !== "UNLIMITED"}
checked={value && value !== "UNLIMITED"}
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
<RadioGroup.Root
defaultValue={watchPeriodType}
@@ -233,7 +327,7 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
</div>
)}
{period.type === "RANGE" && (
<div className="inline-flex space-x-2 ltr:ml-2 rtl:mr-2 rtl:space-x-reverse">
<div className="inline-flex space-x-2 ltr:ml-2 ltr:mr-2 rtl:ml-2 rtl:space-x-reverse">
<Controller
name="periodDates"
control={formMethods.control}
@@ -253,7 +347,9 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
/>
</div>
)}
{period.suffix ? <span className="ltr:ml-2 rtl:mr-2">&nbsp;{period.suffix}</span> : null}
{period.suffix ? (
<span className="ltr:ml-2 ltr:mr-2 rtl:ml-2">&nbsp;{period.suffix}</span>
) : null}
</div>
);
})}
@@ -316,7 +412,9 @@ const BookingLimits = () => {
.map(([key, bookingAmount]) => {
const bookingLimitKey = key as BookingLimitsKey;
return (
<div className="mb-2 flex items-center space-x-2 text-sm" key={bookingLimitKey}>
<div
className="mb-2 flex items-center space-x-2 text-sm rtl:space-x-reverse"
key={bookingLimitKey}>
<Input
id={`${bookingLimitKey}-limit`}
type="number"
@@ -1,11 +1,10 @@
import { EventTypeSetupInfered } from "pages/event-types/[type]";
import { useState } from "react";
import { EventTypeSetupProps } from "pages/event-types/[type]";
import getStripeAppData from "@calcom/lib/getStripeAppData";
import RecurringEventController from "./RecurringEventController";
export const EventRecurringTab = ({ eventType }: Pick<EventTypeSetupInfered, "eventType">) => {
export const EventRecurringTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
const stripeAppData = getStripeAppData(eventType);
const requirePayment = stripeAppData.price > 0;
@@ -1,22 +1,22 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
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 { getEventLocationType, EventLocationType } 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 { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components";
import { Label, TextField } from "@calcom/ui/components/form";
import { Select, Skeleton } from "@calcom/ui/v2";
import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { slugify } from "@lib/slugify";
import { EditLocationDialog } from "@components/v2/eventtype/EditLocationDialog";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
type OptionTypeBase = {
label: string;
@@ -25,13 +25,30 @@ type OptionTypeBase = {
};
export const EventSetupTab = (
props: Pick<EventTypeSetupInfered, "eventType" | "locationOptions" | "team" | "teamMembers">
props: Pick<EventTypeSetupProps, "eventType" | "locationOptions" | "team" | "teamMembers">
) => {
const { t } = useLocale();
const formMethods = useFormContext<FormValues>();
const { eventType, locationOptions, team } = props;
const [showLocationModal, setShowLocationModal] = useState(false);
const [editingLocationType, setEditingLocationType] = useState<string>("");
const [selectedLocation, setSelectedLocation] = useState<OptionTypeBase | undefined>(undefined);
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({
value: mins,
label: t("multiple_duration_mins", { count: mins }),
}));
const [selectedMultipleDuration, setSelectedMultipleDuration] = useState<
MultiValue<{
value: number;
label: string;
}>
>(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value)));
const [defaultDuration, setDefaultDuration] = useState(
selectedMultipleDuration.find((opt) => opt.value === eventType.length) ?? null
);
const openLocationModal = (type: EventLocationType["type"]) => {
setSelectedLocation(locationOptions.find((option) => option.value === type));
@@ -46,14 +63,23 @@ export const EventSetupTab = (
);
};
const addLocation = (newLocationType: EventLocationType["type"], details = {}) => {
const existingIdx = formMethods.getValues("locations").findIndex((loc) => newLocationType === loc.type);
const saveLocation = (newLocationType: EventLocationType["type"], details = {}) => {
const locationType = editingLocationType !== "" ? editingLocationType : newLocationType;
const existingIdx = formMethods.getValues("locations").findIndex((loc) => locationType === loc.type);
if (existingIdx !== -1) {
const copy = formMethods.getValues("locations");
copy[existingIdx] = {
...formMethods.getValues("locations")[existingIdx],
...details,
};
if (editingLocationType !== "") {
copy[existingIdx] = {
...details,
type: newLocationType,
};
} else {
copy[existingIdx] = {
...formMethods.getValues("locations")[existingIdx],
...details,
};
}
formMethods.setValue("locations", copy);
} else {
formMethods.setValue(
@@ -61,6 +87,8 @@ export const EventSetupTab = (
formMethods.getValues("locations").concat({ type: newLocationType, ...details })
);
}
setEditingLocationType("");
setShowLocationModal(false);
};
@@ -104,6 +132,7 @@ export const EventSetupTab = (
{validLocations.length === 0 && (
<div className="flex">
<Select
placeholder={t("select")}
options={locationOptions}
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
@@ -118,7 +147,7 @@ export const EventSetupTab = (
if (eventLocationType.organizerInputType) {
openLocationModal(newLocationType);
} else {
addLocation(newLocationType);
saveLocation(newLocationType);
}
}
}}
@@ -138,11 +167,11 @@ export const EventSetupTab = (
<div key={index} className="flex flex-grow items-center">
<img
src={eventLocationType.iconUrl}
className="h-6 w-6"
className="h-4 w-4"
alt={`${eventLocationType.label} logo`}
/>
<span className="truncate text-sm ltr:ml-2 rtl:mr-2">
{location[eventLocationType.defaultValueVariable] || eventLocationType.label}
<span className="truncate text-sm ltr:ml-1 rtl:mr-1">
{t(location[eventLocationType.defaultValueVariable] || eventLocationType.label)}
</span>
</div>
<div className="flex">
@@ -153,6 +182,7 @@ export const EventSetupTab = (
locationFormMethods.unregister("locationLink");
locationFormMethods.unregister("locationAddress");
locationFormMethods.unregister("locationPhoneNumber");
setEditingLocationType(location.type);
openLocationModal(location.type);
}}
aria-label={t("edit")}
@@ -167,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)}>
@@ -185,7 +232,7 @@ export const EventSetupTab = (
<div className="space-y-8">
<TextField
required
label={t("Title")}
label={t("title")}
defaultValue={eventType.title}
{...formMethods.register("title")}
/>
@@ -209,21 +256,96 @@ export const EventSetupTab = (
setValueAs: (v) => slugify(v),
})}
/>
<TextField
required
name="length"
type="number"
label={t("duration")}
addOnSuffix={<>{t("minutes")}</>}
defaultValue={eventType.length ?? 15}
onChange={(e) => {
formMethods.setValue("length", Number(e.target.value));
}}
/>
{multipleDuration ? (
<div className="space-y-4">
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("available_durations")}
</Skeleton>
<Select
isMulti
defaultValue={selectedMultipleDuration}
name="metadata.multipleDuration"
isSearchable={false}
className="h-auto !min-h-[36px] text-sm"
options={multipleDurationOptions}
value={selectedMultipleDuration}
onChange={(options) => {
let newOptions = [...options];
newOptions = newOptions.sort((a, b) => {
return a?.value - b?.value;
});
const values = newOptions.map((opt) => opt.value);
setMultipleDuration(values);
setSelectedMultipleDuration(newOptions);
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
if (newOptions.length > 0) {
setDefaultDuration(newOptions[0]);
formMethods.setValue("length", newOptions[0].value);
} else {
setDefaultDuration(null);
}
}
if (newOptions.length === 1 && defaultDuration === null) {
setDefaultDuration(newOptions[0]);
formMethods.setValue("length", newOptions[0].value);
}
formMethods.setValue("metadata.multipleDuration", values);
}}
/>
</div>
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("default_duration")}
</Skeleton>
<Select
value={defaultDuration}
isSearchable={false}
name="length"
className="text-sm"
noOptionsMessage={() => t("default_duration_no_options")}
options={selectedMultipleDuration}
onChange={(option) => {
setDefaultDuration(
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
);
if (option) formMethods.setValue("length", option.value);
}}
/>
</div>
</div>
) : (
<TextField
required
type="number"
label={t("duration")}
defaultValue={eventType.length ?? 15}
{...formMethods.register("length")}
addOnSuffix={<>{t("minutes")}</>}
/>
)}
<div className="!mt-4 [&_label]:my-1 [&_label]:font-normal">
<SettingsToggle
title={t("allow_booker_to_select_duration")}
checked={multipleDuration !== undefined}
onCheckedChange={() => {
if (multipleDuration !== undefined) {
setMultipleDuration(undefined);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("length", eventType.length);
} else {
setMultipleDuration([]);
formMethods.setValue("metadata.multipleDuration", []);
formMethods.setValue("length", 0);
}
}}
/>
</div>
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("location")}
</Skeleton>
<Controller
name="locations"
control={formMethods.control}
@@ -237,12 +359,13 @@ export const EventSetupTab = (
<EditLocationDialog
isOpenDialog={showLocationModal}
setShowLocationModal={setShowLocationModal}
saveLocation={addLocation}
saveLocation={saveLocation}
defaultValues={formMethods.getValues("locations")}
selection={
selectedLocation ? { value: selectedLocation.value, label: selectedLocation.label } : undefined
selectedLocation ? { value: selectedLocation.value, label: t(selectedLocation.label) } : undefined
}
setSelectedLocation={setSelectedLocation}
setEditingLocationType={setEditingLocationType}
/>
</div>
);
@@ -1,14 +1,12 @@
import { SchedulingType } from "@prisma/client/";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo } from "react";
import { Controller, useFormContext } from "react-hook-form";
import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTeamSelect";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { Button, Avatar } from "@calcom/ui/components";
import { Label } from "@calcom/ui/components/form";
import { Select, CheckedTeamSelect } from "@calcom/ui/v2";
import { Label, Select } from "@calcom/ui";
interface IMemberToValue {
id: number | null;
@@ -28,8 +26,7 @@ export const EventTeamTab = ({
eventType,
team,
teamMembers,
currentUserMembership,
}: Pick<EventTypeSetupInfered, "eventType" | "teamMembers" | "team" | "currentUserMembership">) => {
}: Pick<EventTypeSetupProps, "eventType" | "teamMembers" | "team">) => {
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
@@ -104,48 +101,3 @@ export const EventTeamTab = ({
</div>
);
};
const TeamProfileCard = ({
id,
name,
hasPermsToDelete,
email,
username,
}: {
id: number;
name: string | null;
email: string;
username: string | null;
hasPermsToDelete: boolean;
}) => {
const formMethods = useFormContext<FormValues>();
const avatar = `${WEBAPP_URL}/${username}/avatar.png`;
return (
<div className="flex items-center border border-gray-300 px-6 py-4 first:rounded-t-md last:rounded-b-md only:rounded-md">
<Avatar imageSrc={avatar} gravatarFallbackMd5="ajsdkwakhsd1231" size="md" alt="Team Image" />
<div className="flex flex-col pl-3">
<div className="">
<span className="pr-2 text-sm font-semibold leading-none text-black">{name}</span>
</div>
<p className="text-sm font-normal leading-normal text-gray-700">{email}</p>
</div>
{hasPermsToDelete && (
<div className="ml-auto">
<Button
type="button"
size="icon"
color="destructive"
StartIcon={Icon.FiTrash}
onClick={() => {
formMethods.setValue(
"users",
formMethods.getValues("users").filter((user) => user !== id.toString())
);
}}
/>
</div>
)}
</div>
);
};
@@ -0,0 +1,179 @@
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { TbWebhook } from "react-icons/tb";
import { WebhookForm } from "@calcom/features/webhooks/components";
import { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
import WebhookListItem from "@calcom/features/webhooks/components/WebhookListItem";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Webhook } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button, Dialog, DialogContent, EmptyScreen, Icon, showToast } from "@calcom/ui";
export const EventTeamWebhooksTab = ({
eventType,
team,
}: Pick<EventTypeSetupProps, "eventType" | "team">) => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: webhooks } = trpc.viewer.webhook.list.useQuery({ eventTypeId: eventType.id });
const { data: installedApps, isLoading } = trpc.viewer.integrations.useQuery({
variant: "other",
onlyInstalled: true,
});
const [createModalOpen, setCreateModalOpen] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
const [webhookToEdit, setWebhookToEdit] = useState<Webhook>();
const subscriberUrlReserved = (subscriberUrl: string, id?: string): boolean => {
return !!webhooks?.find(
(webhook) => webhook.subscriberUrl === subscriberUrl && (!id || webhook.id !== id)
);
};
const editWebhookMutation = trpc.viewer.webhook.edit.useMutation({
async onSuccess() {
setEditModalOpen(false);
await utils.viewer.webhook.list.invalidate();
showToast(t("webhook_updated_successfully"), "success");
},
onError(error) {
showToast(`${error.message}`, "error");
},
});
const createWebhookMutation = trpc.viewer.webhook.create.useMutation({
async onSuccess() {
showToast(t("webhook_created_successfully"), "success");
await utils.viewer.webhook.list.invalidate();
setCreateModalOpen(false);
},
onError(error) {
showToast(`${error.message}`, "error");
},
});
const onCreateWebhook = async (values: WebhookFormSubmitData) => {
if (subscriberUrlReserved(values.subscriberUrl, values.id)) {
showToast(t("webhook_subscriber_url_reserved"), "error");
return;
}
if (!values.payloadTemplate) {
values.payloadTemplate = null;
}
createWebhookMutation.mutate({
subscriberUrl: values.subscriberUrl,
eventTriggers: values.eventTriggers,
active: values.active,
payloadTemplate: values.payloadTemplate,
secret: values.secret,
eventTypeId: eventType.id,
});
};
const NewWebhookButton = () => {
const { t } = useLocale();
return (
<Button
color="secondary"
data-testid="new_webhook"
StartIcon={Icon.FiPlus}
onClick={() => setCreateModalOpen(true)}>
{t("new_webhook")}
</Button>
);
};
return (
<div>
{team && webhooks && !isLoading && (
<>
<div>
<div>
<>
{webhooks.length ? (
<>
<div className="mt-4 mb-8 rounded-md border">
{webhooks.map((webhook, index) => {
return (
<WebhookListItem
key={webhook.id}
webhook={webhook}
lastItem={webhooks.length === index + 1}
onEditWebhook={() => {
setEditModalOpen(true);
setWebhookToEdit(webhook);
}}
/>
);
})}
</div>
<NewWebhookButton />
</>
) : (
<EmptyScreen
Icon={TbWebhook}
headline={t("create_your_first_webhook")}
description={t("create_your_first_team_webhook_description", { appName: APP_NAME })}
buttonRaw={<NewWebhookButton />}
/>
)}
</>
</div>
</div>
{/* New webhook dialog */}
<Dialog open={createModalOpen} onOpenChange={(isOpen) => !isOpen && setCreateModalOpen(false)}>
<DialogContent title={t("create_webhook")} description={t("create_webhook_team_event_type")}>
<WebhookForm
onSubmit={onCreateWebhook}
onCancel={() => setCreateModalOpen(false)}
apps={installedApps?.items.map((app) => app.slug)}
/>
</DialogContent>
</Dialog>
{/* Edit webhook dialog */}
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
<DialogContent title={t("edit_webhook")}>
<WebhookForm
webhook={webhookToEdit}
apps={installedApps?.items.map((app) => app.slug)}
onCancel={() => setEditModalOpen(false)}
onSubmit={(values: WebhookFormSubmitData) => {
if (subscriberUrlReserved(values.subscriberUrl, webhookToEdit?.id || "")) {
showToast(t("webhook_subscriber_url_reserved"), "error");
return;
}
if (values.changeSecret) {
values.secret = values.newSecret.length ? values.newSecret : null;
}
if (!values.payloadTemplate) {
values.payloadTemplate = null;
}
editWebhookMutation.mutate({
id: webhookToEdit?.id || "",
subscriberUrl: values.subscriberUrl,
eventTriggers: values.eventTriggers,
active: values.active,
payloadTemplate: values.payloadTemplate,
secret: values.secret,
eventTypeId: webhookToEdit?.eventTypeId || undefined,
});
}}
/>
</DialogContent>
</Dialog>
</>
)}
</div>
);
};
@@ -1,38 +1,44 @@
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useMemo, useState } from "react";
import { Loader } from "react-feather";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo, useState, Suspense } from "react";
import { UseFormReturn } from "react-hook-form";
import { TbWebhook } from "react-icons/tb";
import { classNames } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui/Icon";
import { Button, ButtonGroup, Label } from "@calcom/ui/components";
import { HorizontalTabs, showToast, Switch, Tooltip, VerticalTabs } from "@calcom/ui/v2";
import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent";
import { Dialog } from "@calcom/ui/v2/core/Dialog";
import Divider from "@calcom/ui/v2/core/Divider";
import Dropdown, {
import {
Button,
ButtonGroup,
ConfirmationDialogContent,
Dialog,
Divider,
Dropdown,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@calcom/ui/v2/core/Dropdown";
import Shell from "@calcom/ui/v2/core/Shell";
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
import { Skeleton } from "@calcom/ui/v2/core/skeleton";
HorizontalTabs,
Icon,
Label,
Shell,
showToast,
Skeleton,
Switch,
Tooltip,
VerticalDivider,
VerticalTabs,
} from "@calcom/ui";
import { ClientSuspense } from "@components/ClientSuspense";
import { EmbedButton, EmbedDialog } from "@components/Embed";
type Props = {
children: React.ReactNode;
eventType: EventTypeSetupInfered["eventType"];
currentUserMembership: EventTypeSetupInfered["currentUserMembership"];
team: EventTypeSetupInfered["team"];
eventType: EventTypeSetupProps["eventType"];
currentUserMembership: EventTypeSetupProps["currentUserMembership"];
team: EventTypeSetupProps["team"];
disableBorder?: boolean;
enabledAppsNumber: number;
installedAppsNumber: number;
@@ -60,7 +66,7 @@ function getNavigation(props: {
name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`,
icon: Icon.FiCalendar,
info: `Working Hours`, // TODO: Get this from props
info: `default_schedule_name`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",
@@ -115,9 +121,9 @@ function EventTypeSingleLayout({
const hasPermsToDelete = currentUserMembership?.role !== "MEMBER" || !currentUserMembership;
const deleteMutation = trpc.useMutation("viewer.eventTypes.delete", {
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
onSuccess: async () => {
await utils.invalidateQueries(["viewer.eventTypes"]);
await utils.viewer.eventTypes.invalidate();
showToast(t("event_type_deleted_successfully"), "success");
await router.push("/event-types");
setDeleteDialogOpen(false);
@@ -143,13 +149,20 @@ function EventTypeSingleLayout({
enabledWorkflowsNumber,
});
// If there is a team put this navigation item within the tabs
if (team)
if (team) {
navigation.splice(2, 0, {
name: "scheduling_type",
href: `/event-types/${eventType.id}?tabName=team`,
icon: Icon.FiUsers,
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
});
navigation.push({
name: "webhooks",
href: `/event-types/${eventType.id}?tabName=webhooks`,
icon: TbWebhook,
info: `${eventType.webhooks.filter((webhook) => webhook.active).length} ${t("active")}`,
});
}
return navigation;
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team]);
@@ -202,7 +215,6 @@ function EventTypeSingleLayout({
color="secondary"
size="icon"
StartIcon={Icon.FiLink}
combined
tooltip={t("copy_link")}
onClick={() => {
navigator.clipboard.writeText(permalink);
@@ -288,7 +300,7 @@ function EventTypeSingleLayout({
</Button>
</div>
}>
<ClientSuspense fallback={<Loader />}>
<Suspense fallback={<Icon.FiLoader />}>
<div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
<div className="hidden xl:block">
<VerticalTabs
@@ -311,7 +323,7 @@ function EventTypeSingleLayout({
</div>
</div>
</div>
</ClientSuspense>
</Suspense>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
isLoading={deleteMutation.isLoading}
@@ -1 +1 @@
export { default } from "@calcom/features/ee/workflows/components/v2/EventWorkflowsTab";
export { default } from "@calcom/features/ee/workflows/components/EventWorkflowsTab";
@@ -5,8 +5,7 @@ import { useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Frequency } from "@calcom/prisma/zod-utils";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Alert } from "@calcom/ui/Alert";
import { Select, SettingsToggle } from "@calcom/ui/v2";
import { Alert, Select, SettingsToggle } from "@calcom/ui";
type RecurringEventControllerProps = {
recurringEvent: RecurringEvent | null;
@@ -58,7 +57,7 @@ export default function RecurringEventController({
{recurringEventState && (
<div data-testid="recurring-event-collapsible" className="text-sm">
<div className="flex items-center">
<p className="mr-2 text-neutral-900">{t("repeats_every")}</p>
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("repeats_every")}</p>
<input
type="number"
min="1"
@@ -90,7 +89,7 @@ export default function RecurringEventController({
/>
</div>
<div className="mt-4 flex items-center">
<p className="mr-2 text-neutral-900">{t("for_a_maximum_of")}</p>
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("for_a_maximum_of")}</p>
<input
type="number"
min="1"
@@ -106,7 +105,7 @@ export default function RecurringEventController({
setRecurringEventState(newVal);
}}
/>
<p className="mr-2 text-neutral-900">
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">
{t("events", {
count: recurringEventState.count,
})}
@@ -0,0 +1,156 @@
import * as RadioGroup from "@radix-ui/react-radio-group";
import { UnitTypeLongPlural } from "dayjs";
import { Trans } from "next-i18next";
import type { FormValues } from "pages/event-types/[type]";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import z from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Alert, Input, Label, SettingsToggle } from "@calcom/ui";
type RequiresConfirmationControllerProps = {
metadata: z.infer<typeof EventTypeMetaDataSchema>;
requiresConfirmation: boolean;
onRequiresConfirmation: Dispatch<SetStateAction<boolean>>;
seatsEnabled: boolean;
};
export default function RequiresConfirmationController({
metadata,
requiresConfirmation,
onRequiresConfirmation,
seatsEnabled,
}: RequiresConfirmationControllerProps) {
const { t } = useLocale();
const [requiresConfirmationSetup, setRequiresConfirmationSetup] = useState(
metadata?.requiresConfirmationThreshold
);
const defaultRequiresConfirmationSetup = { time: 30, unit: "minutes" as UnitTypeLongPlural };
const formMethods = useFormContext<FormValues>();
useEffect(() => {
if (!requiresConfirmation) {
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
}
}, [requiresConfirmation]);
return (
<div className="block items-start sm:flex">
<div className={!seatsEnabled ? "w-full" : ""}>
{seatsEnabled ? (
<Alert severity="warning" title="Seats option doesn't support confirmation requirement" />
) : (
<Controller
name="requiresConfirmation"
control={formMethods.control}
render={() => (
<SettingsToggle
title={t("requires_confirmation")}
description={t("requires_confirmation_description")}
checked={requiresConfirmation}
onCheckedChange={(val) => {
formMethods.setValue("requiresConfirmation", val);
onRequiresConfirmation(val);
}}>
<RadioGroup.Root
defaultValue={
requiresConfirmation
? requiresConfirmationSetup === undefined
? "always"
: "notice"
: undefined
}
onValueChange={(val) => {
if (val === "always") {
formMethods.setValue("requiresConfirmation", true);
onRequiresConfirmation(true);
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
setRequiresConfirmationSetup(undefined);
} else if (val === "notice") {
formMethods.setValue("requiresConfirmation", true);
onRequiresConfirmation(true);
formMethods.setValue(
"metadata.requiresConfirmationThreshold",
requiresConfirmationSetup || defaultRequiresConfirmationSetup
);
}
}}>
<div className="-ml-2 flex flex-col flex-wrap justify-start gap-y-2 space-y-2">
<div className="flex items-center">
<RadioGroup.Item
id="always"
value="always"
className="min-w-4 flex h-4 w-4 cursor-pointer items-center rounded-full border border-black bg-white focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full after:bg-black" />
</RadioGroup.Item>
<Label htmlFor="always" className="!m-0 flex items-center">
{t("always_requires_confirmation")}
</Label>
</div>
<div className="flex items-center">
<RadioGroup.Item
id="notice"
value="notice"
className="min-w-4 flex h-4 w-4 cursor-pointer items-center rounded-full border border-black bg-white focus:border-2 focus:outline-none ltr:mr-2 rtl:ml-2">
<RadioGroup.Indicator className="relative flex h-4 w-4 items-center justify-center after:block after:h-2 after:w-2 after:rounded-full after:bg-black" />
</RadioGroup.Item>
<Label htmlFor="notice" className="!m-0 flex items-center">
<Trans
i18nKey="when_booked_with_less_than_notice"
defaults="When booked with less than <time></time> notice"
components={{
time: (
<div className="mx-2 flex">
<Input
type="number"
min={1}
onChange={(evt) => {
const val = Number(evt.target?.value);
setRequiresConfirmationSetup({
unit:
requiresConfirmationSetup?.unit ??
defaultRequiresConfirmationSetup.unit,
time: val,
});
formMethods.setValue("metadata.requiresConfirmationThreshold.time", val);
}}
className="!m-0 block w-16 rounded-md border-gray-300 text-sm [appearance:textfield]"
defaultValue={metadata?.requiresConfirmationThreshold?.time || 30}
/>
<select
onChange={(evt) => {
const val = evt.target.value as UnitTypeLongPlural;
setRequiresConfirmationSetup({
time:
requiresConfirmationSetup?.time ??
defaultRequiresConfirmationSetup.time,
unit: val,
});
formMethods.setValue("metadata.requiresConfirmationThreshold.unit", val);
}}
className="ml-2 block h-9 rounded-md border-gray-300 py-2 pl-3 pr-10 text-sm focus:outline-none"
defaultValue={
metadata?.requiresConfirmationThreshold?.unit ||
defaultRequiresConfirmationSetup.unit
}>
<option value="minutes">{t("minute_timeUnit")}</option>
<option value="hours">{t("hour_timeUnit")}</option>
</select>
</div>
),
}}
/>
</Label>
</div>
</div>
</RadioGroup.Root>
</SettingsToggle>
)}
/>
)}
</div>
</div>
);
}
@@ -1,13 +1,11 @@
import { SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { Icon } from "@calcom/ui/Icon";
import { Icon, SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
/** @deprecated Use `apps/web/components/v2/eventtype/SkeletonLoader.tsx` */
function SkeletonLoader() {
return (
<SkeletonContainer>
<div className="mb-4 flex items-center">
<SkeletonAvatar width="8" height="8" />
<div className="space-y-1">
<SkeletonAvatar className="h-8 w-8" />
<div className="flex flex-col space-y-1">
<SkeletonText className="h-4 w-16" />
<SkeletonText className="h-4 w-24" />
</div>
@@ -28,17 +26,17 @@ function SkeletonItem() {
<li className="group flex w-full items-center justify-between px-4 py-4 sm:px-6">
<div className="flex-grow truncate text-sm">
<div>
<SkeletonText width="32" height="5" />
<SkeletonText className="h-5 w-32" />
</div>
<div className="">
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
<li className="flex items-center whitespace-nowrap">
<Icon.FiClock className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<SkeletonText width="12" height="4" />
<SkeletonText className="h-4 w-12" />
</li>
<li className="flex items-center whitespace-nowrap">
<Icon.FiUser className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
<SkeletonText width="16" height="4" />
<SkeletonText className="h-4 w-12" />
</li>
</ul>
</div>
@@ -1,7 +1,7 @@
import { InstallAppButtonWithoutPlanCheck } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import { Button } from "@calcom/ui/components/button";
import { Button } from "@calcom/ui";
interface ICalendarItem {
title: string;
@@ -1,8 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import { trpc } from "@calcom/trpc/react";
import { Switch } from "@calcom/ui/v2";
import showToast from "@calcom/ui/v2/core/notifications";
import { showToast, Switch } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -58,7 +57,7 @@ const CalendarSwitch = (props: ICalendarSwitchProps) => {
},
{
async onSettled() {
await utils.invalidateQueries(["viewer.integrations"]);
await utils.viewer.integrations.invalidate();
},
onError() {
showToast(`Something went wrong when toggling "${title}""`, "error");
@@ -1,16 +1,16 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferMutationInput, trpc } from "@calcom/trpc/react";
import { RouterInputs, trpc } from "@calcom/trpc/react";
import DestinationCalendarSelector from "@components/DestinationCalendarSelector";
interface ICreateEventsOnCalendarSelectProps {
calendar?: inferMutationInput<"viewer.setDestinationCalendar"> | null;
calendar?: RouterInputs["viewer"]["setDestinationCalendar"] | null;
}
const CreateEventsOnCalendarSelect = (props: ICreateEventsOnCalendarSelectProps) => {
const { calendar } = props;
const { t } = useLocale();
const mutation = trpc.useMutation(["viewer.setDestinationCalendar"]);
const mutation = trpc.viewer.setDestinationCalendar.useMutation();
return (
<>
@@ -3,8 +3,7 @@ import { ArrowRightIcon } from "@heroicons/react/solid";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { List } from "@calcom/ui/List";
import { SkeletonAvatar, SkeletonText, SkeletonButton } from "@calcom/ui/v2";
import { SkeletonAvatar, SkeletonText, SkeletonButton, List } from "@calcom/ui";
import { CalendarItem } from "../components/CalendarItem";
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
@@ -16,12 +15,9 @@ interface IConnectCalendarsProps {
const ConnectedCalendars = (props: IConnectCalendarsProps) => {
const { nextStep } = props;
const queryConnectedCalendars = trpc.useQuery(["viewer.connectedCalendars"]);
const queryConnectedCalendars = trpc.viewer.connectedCalendars.useQuery();
const { t } = useLocale();
const queryIntegrations = trpc.useQuery([
"viewer.integrations",
{ variant: "calendar", onlyInstalled: false },
]);
const queryIntegrations = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
const firstCalendar = queryConnectedCalendars.data?.connectedCalendars.find(
(item) => item.calendars && item.calendars?.length > 0
@@ -7,8 +7,7 @@ import { DEFAULT_SCHEDULE } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc, TRPCClientErrorLike } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button } from "@calcom/ui/components/button";
import { Form } from "@calcom/ui/form/fields";
import { Button, Form } from "@calcom/ui";
interface ISetupAvailabilityProps {
nextStep: () => void;
@@ -24,9 +23,12 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
const router = useRouter();
let queryAvailability;
if (defaultScheduleId) {
queryAvailability = trpc.useQuery(["viewer.availability.schedule", { scheduleId: defaultScheduleId }], {
enabled: router.isReady,
});
queryAvailability = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId: defaultScheduleId },
{
enabled: router.isReady,
}
);
}
const availabilityForm = useForm({
@@ -43,8 +45,8 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
nextStep();
},
};
const createSchedule = trpc.useMutation("viewer.availability.schedule.create", mutationOptions);
const updateSchedule = trpc.useMutation("viewer.availability.schedule.update", mutationOptions);
const createSchedule = trpc.viewer.availability.schedule.create.useMutation(mutationOptions);
const updateSchedule = trpc.viewer.availability.schedule.update.useMutation(mutationOptions);
return (
<Form
className="w-full bg-white text-black dark:bg-opacity-5 dark:text-white"
@@ -4,24 +4,20 @@ import { FormEvent, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components";
import { TextArea } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/v2";
import ImageUploader from "@calcom/ui/v2/core/ImageUploader";
import { Button, ImageUploader, showToast, TextArea } from "@calcom/ui";
import { Avatar } from "@calcom/ui";
import { AvatarSSR } from "@components/ui/AvatarSSR";
interface IUserProfile {
user?: User;
}
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
type FormData = {
bio: string;
};
interface IUserProfileProps {
user: IOnboardingPageProps["user"];
}
const UserProfile = (props: IUserProfile) => {
const UserProfile = (props: IUserProfileProps) => {
const { user } = props;
const { t } = useLocale();
const avatarRef = useRef<HTMLInputElement>(null!);
@@ -32,17 +28,17 @@ const UserProfile = (props: IUserProfile) => {
handleSubmit,
formState: { errors },
} = useForm<FormData>({ defaultValues: { bio: user?.bio || "" } });
const { data: eventTypes } = trpc.useQuery(["viewer.eventTypes.list"]);
const { data: eventTypes } = trpc.viewer.eventTypes.list.useQuery();
const [imageSrc, setImageSrc] = useState<string>(user?.avatar || "");
const utils = trpc.useContext();
const router = useRouter();
const createEventType = trpc.useMutation("viewer.eventTypes.create");
const createEventType = trpc.viewer.eventTypes.create.useMutation();
const mutation = trpc.useMutation("viewer.updateProfile", {
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async (_data, context) => {
if (context.avatar) {
showToast(t("your_user_profile_updated_successfully"), "success");
await utils.refetchQueries(["viewer.me"]);
await utils.viewer.me.refetch();
} else {
try {
if (eventTypes?.length === 0) {
@@ -56,7 +52,7 @@ const UserProfile = (props: IUserProfile) => {
console.error(error);
}
await utils.refetchQueries(["viewer.me"]);
await utils.viewer.me.refetch();
router.push("/");
}
},
@@ -103,7 +99,14 @@ const UserProfile = (props: IUserProfile) => {
return (
<form onSubmit={onSubmit}>
<div className="flex flex-row items-center justify-start rtl:justify-end">
{user && <AvatarSSR user={user} alt="Profile picture" className="h-16 w-16" />}
{user && (
<Avatar
alt={user.username || "user avatar"}
gravatarFallbackMd5={user.emailMd5}
size="lg"
imageSrc={imageSrc}
/>
)}
<input
ref={avatarRef}
type="hidden"
@@ -1,18 +1,18 @@
import { ArrowRightIcon } from "@heroicons/react/outline";
import { useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useState } from "react";
import { useForm } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { User } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components/button";
import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
import { Button, TimezoneSelect } from "@calcom/ui";
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
interface IUserSettingsProps {
user: User;
user: IOnboardingPageProps["user"];
nextStep: () => void;
}
@@ -24,11 +24,9 @@ const UserSettings = (props: IUserSettingsProps) => {
register,
handleSubmit,
formState: { errors },
control,
} = useForm({
defaultValues: {
name: user?.name || "",
username: user?.username || "",
},
reValidateMode: "onChange",
});
@@ -36,45 +34,25 @@ const UserSettings = (props: IUserSettingsProps) => {
const utils = trpc.useContext();
const onSuccess = async () => {
await utils.invalidateQueries(["viewer.me"]);
await utils.viewer.me.invalidate();
nextStep();
};
const mutation = trpc.useMutation("viewer.updateProfile", {
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: onSuccess,
});
const { data: stripeCustomer } = trpc.useQuery(["viewer.stripeCustomer"]);
const paymentRequired = stripeCustomer?.isPremium ? !stripeCustomer?.paidForPremium : false;
const onSubmit = handleSubmit((data) => {
if (paymentRequired) {
return;
}
mutation.mutate({
name: data.name,
timeZone: selectedTimeZone,
});
});
const [currentUsername, setCurrentUsername] = useState(user.username || "");
return (
<form onSubmit={onSubmit}>
<div className="space-y-6">
{/* Username textfield */}
<Controller
control={control}
name="username"
render={({ field: { value, ref, onChange } }) => (
<UsernameAvailability
readonly={true}
currentUsername={currentUsername}
setCurrentUsername={setCurrentUsername}
inputUsernameValue={value}
usernameRef={ref}
setInputUsernameValue={onChange}
user={user}
/>
)}
/>
<UsernameAvailabilityField user={user} />
{/* Full name textfield */}
<div className="w-full">
@@ -1,290 +0,0 @@
import { useMutation } from "@tanstack/react-query";
import { Fragment } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import { List } from "@calcom/ui/List";
import { ShellSubHeading } from "@calcom/ui/Shell";
import Switch from "@calcom/ui/Switch";
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
import showToast from "@calcom/ui/v2/core/notifications";
import { QueryCell } from "@lib/QueryCell";
import AdditionalCalendarSelector from "@components/AdditionalCalendarSelector";
import DestinationCalendarSelector from "@components/DestinationCalendarSelector";
import DisconnectIntegration from "./DisconnectIntegration";
import IntegrationListItem from "./IntegrationListItem";
import SubHeadingTitleWithConnections from "./SubHeadingTitleWithConnections";
type Props = {
onChanged: () => unknown | Promise<unknown>;
fromOnboarding?: boolean;
};
function CalendarSwitch(props: {
type: string;
externalId: string;
title: string;
defaultSelected: boolean;
}) {
const utils = trpc.useContext();
const mutation = useMutation<
unknown,
unknown,
{
isOn: boolean;
}
>(
async ({ isOn }) => {
const body = {
integration: props.type,
externalId: props.externalId,
};
if (isOn) {
const res = await fetch("/api/availability/calendar", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
} else {
const res = await fetch("/api/availability/calendar", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error("Something went wrong");
}
}
},
{
async onSettled() {
await utils.invalidateQueries(["viewer.integrations"]);
},
onError() {
showToast(`Something went wrong when toggling "${props.title}""`, "error");
},
}
);
return (
<div className="py-1">
<Switch
key={props.externalId}
name="enabled"
label={props.title}
defaultChecked={props.defaultSelected}
onCheckedChange={(isOn: boolean) => {
mutation.mutate({ isOn });
}}
/>
</div>
);
}
function CalendarList(props: Props) {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.integrations", { variant: "calendar", onlyInstalled: false }]);
return (
<QueryCell
query={query}
success={({ data }) => (
<List>
{data.items.map((item) => (
<IntegrationListItem
name={item.name}
slug={item.slug}
key={item.title}
title={item.title}
logo={item.logo}
description={item.description}
actions={
<InstallAppButton
type={item.type}
render={(buttonProps) => (
<Button color="secondary" {...buttonProps}>
{t("connect")}
</Button>
)}
onChanged={() => props.onChanged()}
/>
}
/>
))}
</List>
)}
/>
);
}
function ConnectedCalendarsList(props: Props) {
const { t } = useLocale();
const query = trpc.useQuery(["viewer.connectedCalendars"], { suspense: true });
const { fromOnboarding } = props;
return (
<QueryCell
query={query}
empty={() => null}
success={({ data }) => {
if (!data.connectedCalendars.length) {
return null;
}
return (
<List>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars ? (
<IntegrationListItem
slug={item.integration.slug}
title={item.integration.title}
logo={item.integration.logo}
description={item.primary?.externalId || "No external Id"}
actions={
<DisconnectIntegration
id={item.credentialId}
externalId={item.primary?.externalId}
render={(btnProps) => (
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
{t("disconnect")}
</Button>
)}
onOpenChange={props.onChanged}
/>
}>
{!fromOnboarding && (
<>
<p className="px-4 pt-4 text-sm text-neutral-500">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-2 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
type={item.integration.type}
defaultSelected={cal.isSelected}
/>
))}
</ul>
</>
)}
</IntegrationListItem>
) : (
<Alert
severity="warning"
title={t("something_went_wrong")}
message={t("calendar_error")}
actions={
<DisconnectIntegration
id={item.credentialId}
render={(btnProps) => (
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
Disconnect
</Button>
)}
onOpenChange={() => props.onChanged()}
/>
}
/>
)}
</Fragment>
))}
</List>
);
}}
/>
);
}
export function CalendarListContainer(props: {
heading?: boolean;
items?: inferQueryOutput<"viewer.integrations">["items"];
fromOnboarding?: boolean;
}) {
const { t } = useLocale();
const { heading = true, fromOnboarding } = props;
const utils = trpc.useContext();
const onChanged = () =>
Promise.allSettled([
utils.invalidateQueries(["viewer.integrations", { variant: "calendar", onlyInstalled: true }], {
exact: true,
}),
utils.invalidateQueries(["viewer.connectedCalendars"]),
]);
const query = trpc.useQuery(["viewer.connectedCalendars"]);
const installedCalendars = trpc.useQuery([
"viewer.integrations",
{ variant: "calendar", onlyInstalled: true },
]);
const mutation = trpc.useMutation("viewer.setDestinationCalendar");
return (
<QueryCell
query={query}
customLoader={<SkeletonLoader className="mt-10" />}
success={({ data }) => {
return (
<>
{(!!data.connectedCalendars.length || !!installedCalendars.data?.items.length) && (
<>
{heading && (
<ShellSubHeading
className="mt-10 mb-0"
title={
<SubHeadingTitleWithConnections
title="Calendars"
numConnections={data.connectedCalendars.length}
/>
}
subtitle={t("configure_how_your_event_types_interact")}
actions={
<div className="flex flex-col xl:flex-row xl:space-x-5">
<div className="sm:min-w-80 block max-w-full">
<DestinationCalendarSelector
onChange={mutation.mutate}
isLoading={mutation.isLoading}
value={data.destinationCalendar?.externalId}
/>
</div>
{!!data.connectedCalendars.length && (
<div className="sm:min-w-80 inline max-w-full">
<AdditionalCalendarSelector isLoading={mutation.isLoading} />
</div>
)}
</div>
}
/>
)}
<ConnectedCalendarsList onChanged={onChanged} fromOnboarding={fromOnboarding} />
</>
)}
{fromOnboarding && (
<>
{!!query.data?.connectedCalendars.length && (
<ShellSubHeading
className="mt-4"
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
/>
)}
<CalendarList onChanged={onChanged} />
</>
)}
</>
);
}}
/>
);
}
@@ -1,57 +0,0 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { ButtonBaseProps } from "@calcom/ui/Button";
import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent";
import { Dialog } from "@calcom/ui/Dialog";
import showToast from "@calcom/ui/v2/core/notifications";
export default function DisconnectIntegration(props: {
/** Integration credential id */
id: number;
externalId?: string;
render: (renderProps: ButtonBaseProps) => JSX.Element;
onOpenChange: (isOpen: boolean) => unknown | Promise<unknown>;
}) {
const { id, externalId = "" } = props;
const { t } = useLocale();
const [modalOpen, setModalOpen] = useState(false);
const mutation = trpc.useMutation("viewer.deleteCredential", {
onSettled: async () => {
await props.onOpenChange(modalOpen);
},
onSuccess: () => {
showToast("Integration deleted successfully", "success");
setModalOpen(false);
},
onError: () => {
throw new Error("Something went wrong");
},
});
return (
<>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<ConfirmationDialogContent
variety="danger"
title={t("remove_app")}
confirmBtnText={t("yes_remove_app")}
cancelBtnText="Cancel"
onConfirm={() => {
mutation.mutate({ id, externalId });
}}>
{t("are_you_sure_you_want_to_remove_this_app")}
</ConfirmationDialogContent>
</Dialog>
{props.render({
onClick() {
setModalOpen(true);
},
disabled: modalOpen,
loading: mutation.isLoading,
})}
</>
);
}
@@ -1,69 +0,0 @@
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { ButtonBaseProps } from "@calcom/ui/Button";
import { Dialog } from "@calcom/ui/Dialog";
import showToast from "@calcom/ui/v2/core/notifications";
import DeleteStripeDialogContent from "@components/dialog/DeleteStripeDialogContent";
export default function DisconnectStripeIntegration(props: {
/** Integration credential id */
id: number;
render: (renderProps: ButtonBaseProps) => JSX.Element;
onOpenChange: (isOpen: boolean) => unknown | Promise<unknown>;
}) {
const [modalOpen, setModalOpen] = useState(false);
const mutation = useMutation(
async (action: string) => {
const res = await fetch("/api/integrations", {
method: "DELETE",
body: JSON.stringify({ id: props.id, action }),
headers: {
"Content-Type": "application/json",
},
});
if (!res.ok) {
throw new Error("Something went wrong");
}
return res.json();
},
{
async onSettled() {
await props.onOpenChange(modalOpen);
},
onSuccess(data) {
showToast(data.message, "success");
setModalOpen(false);
},
}
);
return (
<>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DeleteStripeDialogContent
variety="warning"
title="Disconnect Stripe Integration"
cancelAllBookingsBtnText="Cancel all bookings"
removeBtnText="Remove payment"
cancelBtnText="Cancel"
onConfirm={() => {
mutation.mutate("cancel");
}}
onRemove={() => {
mutation.mutate("remove");
}}>
If you have unpaid and unconfirmed bookings, you must choose to cancel them or remove the required
payment field.
</DeleteStripeDialogContent>
</Dialog>
{props.render({
onClick() {
setModalOpen(true);
},
disabled: modalOpen,
loading: mutation.isLoading,
})}
</>
);
}
@@ -1,39 +0,0 @@
import Link from "next/link";
import { ReactNode } from "react";
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/List";
import classNames from "@lib/classNames";
function IntegrationListItem(props: {
imageSrc?: string;
slug: string;
name?: string;
title?: string;
description: string;
actions?: ReactNode;
children?: ReactNode;
logo: string;
}): JSX.Element {
const title = props.name || props.title;
return (
<ListItem expanded={!!props.children} className={classNames("flex-col")}>
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-3 rtl:space-x-reverse")}>
{
// eslint-disable-next-line @next/next/no-img-element
props.logo && <img className="h-10 w-10" src={props.logo} alt={title} />
}
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3">
<Link href={"/apps/" + props.slug}>{props.name || title}</Link>
</ListItemTitle>
<ListItemText component="p">{props.description}</ListItemText>
</div>
<div>{props.actions}</div>
</div>
{props.children && <div className="w-full border-t border-gray-200">{props.children}</div>}
</ListItem>
);
}
export default IntegrationListItem;
@@ -1,6 +1,6 @@
import { ReactNode } from "react";
import { Badge } from "@calcom/ui/components/badge";
import { Badge } from "@calcom/ui";
function pluralize(opts: { num: number; plural: string; singular: string }) {
if (opts.num === 0) {
@@ -0,0 +1,49 @@
import { noop } from "lodash";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { Toaster } from "react-hot-toast";
import { StepCard, Steps } from "@calcom/ui";
export default function WizardLayout({
children,
maxSteps = 2,
currentStep = 0,
}: {
children: React.ReactNode;
} & { maxSteps?: number; currentStep?: number }) {
const [meta, setMeta] = useState({ title: "", subtitle: " " });
const router = useRouter();
const { title, subtitle } = meta;
useEffect(() => {
setMeta({
title: window.document.title,
subtitle: window.document.querySelector('meta[name="description"]')?.getAttribute("content") || "",
});
}, [router.asPath]);
return (
<div className="dark:bg-brand dark:text-brand-contrast min-h-screen text-black" data-testid="onboarding">
<div>
<Toaster position="bottom-right" />
</div>
<div className="mx-auto px-4 py-24">
<div className="relative">
<div className="sm:mx-auto sm:w-full sm:max-w-[600px]">
<div className="mx-auto sm:max-w-[520px]">
<header>
<p className="font-cal mb-3 text-[28px] font-medium leading-7">{title}&nbsp;</p>
<p className="font-sans text-sm font-normal text-gray-500">{subtitle}&nbsp;</p>
</header>
<Steps maxSteps={maxSteps} currentStep={currentStep} navigateToStep={noop} />
</div>
<StepCard>{children}</StepCard>
</div>
</div>
</div>
</div>
);
}
export const getLayout = (page: React.ReactElement) => <WizardLayout>{page}</WizardLayout>;
+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 🔥
@@ -1,134 +0,0 @@
import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
import React, { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import Button from "@calcom/ui/Button";
import { useLocale } from "@lib/hooks/useLocale";
import Select from "@components/ui/form/Select";
interface OptionTypeBase {
label: string;
value: EventTypeCustomInputType;
}
interface Props {
onSubmit: SubmitHandler<IFormInput>;
onCancel: () => void;
selectedCustomInput?: EventTypeCustomInput;
}
type IFormInput = EventTypeCustomInput;
const CustomInputTypeForm: FC<Props> = (props) => {
const { t } = useLocale();
const inputOptions: OptionTypeBase[] = [
{ value: EventTypeCustomInputType.TEXT, label: t("text") },
{ value: EventTypeCustomInputType.TEXTLONG, label: t("multiline_text") },
{ value: EventTypeCustomInputType.NUMBER, label: t("number") },
{ value: EventTypeCustomInputType.BOOL, label: t("checkbox") },
];
const { selectedCustomInput } = props;
const defaultValues = selectedCustomInput || { type: inputOptions[0].value };
const { register, control, handleSubmit } = useForm<IFormInput>({
defaultValues,
});
const selectedInputType = useWatch({ name: "type", control });
const selectedInputOption = inputOptions.find((e) => selectedInputType === e.value);
const onCancel = () => {
props.onCancel();
};
return (
<form onSubmit={handleSubmit(props.onSubmit)}>
<div className="mb-2">
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
{t("input_type")}
</label>
<Controller
name="type"
control={control}
render={({ field }) => (
<Select
id="type"
defaultValue={selectedInputOption}
options={inputOptions}
isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 text-sm"
onChange={(option) => option && field.onChange(option.value)}
value={selectedInputOption}
onBlur={field.onBlur}
name={field.name}
/>
)}
/>
</div>
<div className="mb-2">
<label htmlFor="label" className="block text-sm font-medium text-gray-700">
{t("label")}
</label>
<div className="mt-1">
<input
type="text"
id="label"
required
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={selectedCustomInput?.label}
{...register("label", { required: true })}
/>
</div>
</div>
{(selectedInputType === EventTypeCustomInputType.TEXT ||
selectedInputType === EventTypeCustomInputType.TEXTLONG) && (
<div className="mb-2">
<label htmlFor="placeholder" className="block text-sm font-medium text-gray-700">
{t("placeholder")}
</label>
<div className="mt-1">
<input
type="text"
id="placeholder"
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={selectedCustomInput?.placeholder}
{...register("placeholder")}
/>
</div>
</div>
)}
<div className="flex h-5 items-center">
<input
id="required"
type="checkbox"
className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-gray-300 ltr:mr-2 rtl:ml-2"
defaultChecked={selectedCustomInput?.required ?? true}
{...register("required")}
/>
<label htmlFor="required" className="block text-sm font-medium text-gray-700">
{t("is_required")}
</label>
</div>
<input
type="hidden"
id="eventTypeId"
value={selectedCustomInput?.eventTypeId || -1}
{...register("eventTypeId", { valueAsNumber: true })}
/>
<input
type="hidden"
id="id"
value={selectedCustomInput?.id || -1}
{...register("id", { valueAsNumber: true })}
/>
<div className="mt-5 flex space-x-2 sm:mt-4">
<Button onClick={onCancel} type="button" color="secondary" className="ltr:mr-2">
{t("cancel")}
</Button>
<Button type="submit">{t("save")}</Button>
</div>
</form>
);
};
export default CustomInputTypeForm;
@@ -0,0 +1,73 @@
import type { Attendee, Booking, User } from "@prisma/client";
import type { FC } from "react";
import { useMemo } from "react";
import { JsonLd } from "react-schemaorg";
import type { EventReservation, Person, ReservationStatusType } from "schema-dts";
type EventSchemaUser = Pick<User, "name" | "email">;
type EventSchemaAttendee = Pick<Attendee, "name" | "email">;
interface EventReservationSchemaInterface {
reservationId: Booking["uid"];
eventName: Booking["title"];
startTime: Booking["startTime"];
endTime: Booking["endTime"];
organizer: EventSchemaUser | null;
attendees: EventSchemaAttendee[];
location: Booking["location"];
description: Booking["description"];
status: Booking["status"];
}
const EventReservationSchema: FC<EventReservationSchemaInterface> = ({
reservationId,
eventName,
startTime,
endTime,
organizer,
attendees,
location,
description,
status,
}) => {
const reservationStatus = useMemo<ReservationStatusType>(() => {
switch (status) {
case "ACCEPTED":
return "ReservationConfirmed";
case "REJECTED":
case "CANCELLED":
return "ReservationCancelled";
case "PENDING":
return "ReservationPending";
default:
return "ReservationHold";
}
}, [status]);
return (
<JsonLd<EventReservation>
item={{
"@context": "https://schema.org",
"@type": "EventReservation",
reservationId,
reservationStatus,
reservationFor: {
"@type": "Event",
name: eventName,
startDate: startTime.toString(),
endDate: endTime.toString(),
organizer: organizer
? ({ "@type": "Person", name: organizer.name, email: organizer.email } as Person)
: undefined,
attendee: attendees?.map(
(person) => ({ "@type": "Person", name: person.name, email: person.email } as Person)
),
location: location || undefined,
description: description || undefined,
},
}}
/>
);
};
export default EventReservationSchema;
@@ -1,9 +1,8 @@
import React, { SyntheticEvent, useState } from "react";
import { SyntheticEvent, useState } from "react";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import showToast from "@calcom/ui/v2/core/notifications";
import { Button, showToast } from "@calcom/ui";
const ChangePasswordSection = () => {
const [oldPassword, setOldPassword] = useState("");
@@ -3,10 +3,7 @@ import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { PasswordField } from "@calcom/ui/components/form";
import { Form, Label } from "@calcom/ui/form/fields";
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
import TwoFactor from "@components/auth/TwoFactor";
@@ -85,7 +82,7 @@ const DisableTwoFactorAuthModal = ({ onDisable, onCancel }: DisableTwoFactorAuth
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit" className="ltr:ml-2 rtl:mr-2" disabled={isDisabling}>
<Button type="submit" className="ltr:ml-2 ltr:mr-2 rtl:ml-2" disabled={isDisabling}>
{t("disable")}
</Button>
<Button color="secondary" onClick={onCancel}>
@@ -1,21 +1,19 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { Badge } from "@calcom/ui/components/badge";
import showToast from "@calcom/ui/v2/core/notifications";
import { Badge, Button, showToast } from "@calcom/ui";
const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonation: boolean }) => {
const utils = trpc.useContext();
const { t } = useLocale();
const mutation = trpc.useMutation("viewer.updateProfile", {
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async () => {
showToast(t("your_user_profile_updated_successfully"), "success");
await utils.invalidateQueries(["viewer.me"]);
await utils.viewer.me.invalidate();
},
async onSettled() {
await utils.invalidateQueries(["viewer.public.i18n"]);
await utils.viewer.public.i18n.invalidate();
},
});

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