* Turbo fixes

* Make apps single pages public

* fixed /booking skeleton (#2722)

* fixed /booking skeleton

* nit

* Type fixes

* Test fixes

* Update playwright.config.ts

* More test fixes

* Update dynamic-booking-pages.test.ts

* add invite link to Zapier setup page (#2696)

* add invite link and toaster to zapier setup page

* create env variable for invite link and save in database

* fetch invite link form getStaticProps

* add getStaticPath method

* clean code

* Moves app setup and index page

* Moves Loader to ui

* Trying new way to handle dynamic app store pages

* Cleanup

* Update tailwind.config.js

* zapier invite link fixes

* Tests fixes

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>

* Add more embed events (#2719)

* Add more embed events

* Add more embed events

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

* adds availability select loader (#2718)

* Improve logs and Fix unwanted 500 to reduce noise in logs (#2674)

* Improve logs

* Fix unintentional 500

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Change date format for RecurringBookings (#2707)

* Change date format for RecurringBookings

* Missing bookingId query param

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* added giphy description (#2730)

* fixes #2732 (#2732)

* Hotfix : Fix Infinite loading of Bookings (#2729)

* Add more embed events

* Add more embed events

* Fix nextCursor calculation logic

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

* Hotfix: Success page for recurring event (#2725)

* Merge pull request #2672 from calcom/main

v1.5.4

* Turbo fixes

* Make apps single pages public

* Fix preview.html not built and thus served during depooy (#2713)

* Hotfix: Success page layout broken due to duplicate "When" (#2716)

* Update BookingPage.tsx

* Reverting unchaged lines

* Fixing recurrenceRule for ICS files

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

* Fix preview.html not built and thus served during depooy (#2727)

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

* Allow deletion of a disabled event (#2737)

* allows deletion of disabled event

* some visual fixes

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Multiple E2E improvements

* Parallelizes some tests

* Update booking-pages.test.ts

* E2E and paid bookings fixes

* Add 'free' and 'workingElsewhere' as a non-blocking event (#2652)

* Add 'free' and 'workingElsewhere' to non-blocking event - this will allow bookings at these times

* Update CalendarService.ts

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* TODO marks blocking test to be fixed

* Update testUtils.ts

* Update testUtils.ts

* getBusyTimes consolidation

* Fixes delete-me test

* E2E fixing attemps

* Adjusting Zapier endpoints for publishing integration (#2728)

* add /me endpoint for zapier API testing

* remove cacellationReason from listBookings response

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: Omar López <zomars@me.com>

* Fix reschedule not happening in calendar if two calendards are there (#2733)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Increases timeout temporarily

* Merge pull request #2745 from calcom/apps/multiple-categories

Allow apps to belong to multiple categories

* Build fixes

* Populate msteams key in db (#2743)

* Populate msteams key in db

* Fix calendar credentials to teams

* Clarify account dialog

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>

* Improving Email DRYness (#2486)

* Email DRY

* WIP

* Improve email DRYness

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* E2E fixtures (#2747)

* Sign in button should be changed or disabled after click #2654 (#2749)

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

* Fixing ESLint warnings (#2723)

* Fixing warnings

* Reverting and disabling ESLint in some cases

* Reverting Next Images

* Reverting file, bad merge

* Targeting ESLint to line

* Additional warnings

* New warning squished

* More tweaks and major fixes

* Uneeded conf

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

* Fix/avoid multiple schedule deletions (#2602)

* Prevents users from deleting the same schedule multiple times due to delay before the schedule disappears. It also applies the same fix to team disband.

Schedule deletion:
![schedule_deletion_new_behaving](https://user-images.githubusercontent.com/42497300/165126805-b3090268-c1a6-418a-b06e-06bd8446da03.gif)

Team disband:
![team_disband_new_behaving](https://user-images.githubusercontent.com/42497300/165127043-7e083e94-e4c9-4e88-90a2-47d31bdd92e6.gif)

Fixes issue [#2569](https://github.com/calcom/cal.com/issues/2569)

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

**apps/web/components/LightLoader.tsx** → this file was created in order to make a light color loading spinner available. It's necessary when we need to display a loading spinner above dark backgrounds.

**apps/web/components/availability/ScheduleListItem.tsx** → this component was created in order to give a schedule list item its own state.

* Removing a "setTimeout" that was only used for testing purposes

* Adding a code review suggestion to my modifications

* Changing loading style

* Cleanup

* Avoids using unnecessary state

* Revert "Adding a code review suggestion to my modifications"

This reverts commit b5e40062d7.

* Reverts some changes

* Renames isLoading

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

* Added check on create eventtype to see is user has slug that already exists (#2757)

* Added check on create eventtype to see is user has slug that already exists, added error check on onError

* revert yarn.lock back

* Catches prisma known error instead of making an additional query

Co-authored-by: zomars <zomars@me.com>

* Fix white border (#2761)

* fix: remove hardcoded redirect in signin url email verification (#2764)

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>

* Bad UX when user wants to set the default Event Type Title #2245 (#2760)

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

* Skip sending emails in E2E

* Users Phone Number Option (#2669)

* Users Phone Number Option

* Implemented improvments

* Add validation to form

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>

* Fix/unpaid unconfirmed (#2553)

* Fix merge errors

* Errors prettier/prettier

* Update apps/web/pages/api/book/event.ts

Co-authored-by: Miguel Nieto A <39246879+miguelnietoa@users.noreply.github.com>

* Update apps/web/pages/api/book/event.ts

Co-authored-by: Miguel Nieto A <39246879+miguelnietoa@users.noreply.github.com>

* Update apps/web/pages/api/integrations.ts

Co-authored-by: Miguel Nieto A <39246879+miguelnietoa@users.noreply.github.com>

* Fix merge errors

* Errors prettier/prettier

* Update apps/web/pages/api/book/confirm.ts

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

* Modal window before delete stripe integration

* ESLint Report

* Test fixes

Co-authored-by: Miguel Nieto A <39246879+miguelnietoa@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: zomars <zomars@me.com>

* Mutually exclusive options (#2755)

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>

* Readd steps to create a new user #2665 (#2759)

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

* remove redundant conditional expressions (#2756)

* remove redundant conditional expressions

* remove redundant conditional expression

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

* Fix adds redirect callback that support app.cal.com (#2768)

* Fix adds redirect callback that support app.cal.com

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

Check origin of website and baseurl

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

* fix: lint issue extra space removed

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* respect local set timezone and update url on mismatch (#2506)

* ensure `timeZone()` will make its way to the URL

fixes https://github.com/calcom/cal.com/issues/2482

* keep `timeZone()` and the offset from URL in sync

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

* Squashed commit of the following:

commit 27540b09ce
Author: Agusti Fernandez Pardo <me@agusti.me>
Date:   Mon May 16 17:34:13 2022 +0200

    fix: remove hardcoded redirect in signin url email verification (#2764)

    Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>

commit ae15a7d739
Author: Hariom Balhara <hariombalhara@gmail.com>
Date:   Tue May 10 14:30:43 2022 +0530

    Fix time issue

commit 2a5a89fe50
Author: Leo Giovanetti <hello@leog.me>
Date:   Wed May 11 10:21:46 2022 -0300

    Missing fix for success page

commit 2ce1e78053
Author: Leo Giovanetti <hello@leog.me>
Date:   Wed May 11 10:12:59 2022 -0300

    Hotfix: Success page for recurring event (#2725)

    * Merge pull request #2672 from calcom/main

    v1.5.4

    * Turbo fixes

    * Make apps single pages public

    * Fix preview.html not built and thus served during depooy (#2713)

    * Hotfix: Success page layout broken due to duplicate "When" (#2716)

    * Update BookingPage.tsx

    * Reverting unchaged lines

    * Fixing recurrenceRule for ICS files

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

commit 2d6d1cb444
Author: Hariom Balhara <hariombalhara@gmail.com>
Date:   Tue May 10 14:49:46 2022 +0530

    Hotfix: Success page layout broken due to duplicate "When" (#2716)

commit ef68f4f4f8
Author: Hariom Balhara <hariombalhara@gmail.com>
Date:   Tue May 10 10:54:20 2022 +0530

    Fix preview.html not built and thus served during depooy (#2713)

commit 18c28cc3fd
Author: zomars <zomars@me.com>
Date:   Mon May 9 16:17:07 2022 -0600

    Make apps single pages public

commit d40e8caff9
Author: zomars <zomars@me.com>
Date:   Mon May 9 16:08:03 2022 -0600

    Turbo fixes

commit 3161cc4d45
Merge: ed808c3be 4099a477d
Author: zomars <zomars@me.com>
Date:   Mon May 9 14:58:33 2022 -0600

    Merge branch 'main' into production

commit ed808c3be6
Author: Omar López <zomars@me.com>
Date:   Mon May 9 14:56:23 2022 -0600

    Merge pull request #2672 from calcom/main

    v1.5.4

* Typo

* Typo

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

* Apply suggestions from code review

* Alert to describe exclusion of options (#2770)

* Alert to describe exclusion of options

* Update apps/web/pages/event-types/[type].tsx

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

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

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

* Formatting

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>

* fix: split time correctly if the local working hours are just across mid night (#2766)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Update crowdin.yml

* New Crowdin translations by Github Action (#2773)

* New Crowdin translations by Github Action

* Update vital.json

* Update vital.json

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Omar López <zomars@me.com>

* Add Google cal extneral calendar id to booking reference (#2671)

* Set google cal event id to use our uid

* Save calendar external id to bookingRef

* Pass external calendar ids to update and delete

* Create migration

* Fix type errors

* Fix prisma url

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>

* v1.6

* v1.6

* 2FA submit disabled (#2790)

* fixing the hyperlink for open startup (#2777)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Add login event (#2784)

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>

* Fixes infinite loop

* Fixes infinite loop

* Fixes infinite loop

* Update all Yarn dependencies (2022-05-16) (#2769)

* Update all Yarn dependencies (2022-05-16)

* Upgrade dependencies

* Removes deprecated packages

* Upgrades deps

* Updates submodules

* Update yarn.lock

* Linting

* Linting

* Update website

* Build fixes

* TODO: fix this

* Module resolving

* Type fixes

* Intercom fixes on SSG

* Fixes infinite loop

* Upgrades to React 18

* Type fixes

* Locks node version to 14

* Upgrades daily-js

* Readds missing types

* Upgrades playwright

* Noop when intercom is not installed

* Update website

* Removed yarn.lock in favor of monorepo

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>

* Create ci.yml

* Update ci.yml

* Reintroduces typescript-eslint

Buckle up!

* Type fixes

* Update ci.yml

* Update api

* Update admin

* Reusable inferSSRProps

* Linting

* Linting

* Prisma fixes

* Update ci.yml

* Cache testing

* Update e2e.yml

* Update DatePicker.tsx

* Update e2e.yml

* Revert "Linting"

This reverts commit adf817766e.

* Revert "Linting"

This reverts commit 1b59dacd64.

* Linting

* Update e2e.yml

* Ci updates

* Add team Id to hash url (#2803)

* Fix missing tabs - Embed (#2804)

* Fix missing tabs

* Fix Eslint error

* Fix Eslint errors

* Add import statement (#2812)

* Add import statement

* Update apps/docs/next.config.js

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

* Show success page if booking was deleted on calendar (#2808)

* Add exception to 410

* Fix type error

* Add GoogelCalError type

* only show invite link for app.cal.dev (#2807)

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

* fix: update eslint config to test .ts and .js separately (#2805)

* fix: update eslint config

* fix: update ts ignore

* fix: update eslint config

* Update TeamAvailabilityScreen.tsx

* Type fixes

* Update useIntercom.ts

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

* fix: sync api to latest commit (#2810)

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Embed React improvements (#2782)

* Add off support. Add getApi export.

* Add publish command

* Add embed-snippet in prod deps

* Update README

* Update package.json

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Consolidates test-results

* Add vscode tasks.json (#2801)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* save additional inputs as json + view details of booking (#2796)

* move custom inputs from description to own json object

* show custom inputs on success page

* fix type error

* add custom inputs to email and webhook

* add custom inputs to all emails

* add values for custom inputs when rescheduling

* add custom input everywhere description is shown

* fix bug with boolean value

* fix issues with null values

* disable custom inputs and add notes for organizer

* don't show custom input with empty string

* don't show custom inputs with empty string in calender event and email

* add link to booking details page

* redirect to success page to see booking details

* add functionality to cancel and reschedule booking

* fix bookings that require confirmation

* clean code

* fix infinite lopp in useEffect of success page

* show web conference details message when integration as location

* improve design of cancelling event

* clean code

* disable darkmode for organizer on booking details page

* fix dark mode for cancelling booking

* fix build error

* Fixes infinite loop

* Fixes infinite loop

* Fixes infinite loop

* Update all Yarn dependencies (2022-05-16) (#2769)

* Update all Yarn dependencies (2022-05-16)

* Upgrade dependencies

* Removes deprecated packages

* Upgrades deps

* Updates submodules

* Update yarn.lock

* Linting

* Linting

* Update website

* Build fixes

* TODO: fix this

* Module resolving

* Type fixes

* Intercom fixes on SSG

* Fixes infinite loop

* Upgrades to React 18

* Type fixes

* Locks node version to 14

* Upgrades daily-js

* Readds missing types

* Upgrades playwright

* Noop when intercom is not installed

* Update website

* Removed yarn.lock in favor of monorepo

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>

* Create ci.yml

* Update ci.yml

* Reintroduces typescript-eslint

Buckle up!

* Type fixes

* Update ci.yml

* Update api

* Update admin

* Reusable inferSSRProps

* Linting

* Linting

* Prisma fixes

* Update ci.yml

* Cache testing

* Update e2e.yml

* Update DatePicker.tsx

* Update e2e.yml

* Revert "Linting"

This reverts commit adf817766e.

* Revert "Linting"

This reverts commit 1b59dacd64.

* Linting

* Update e2e.yml

* Ci updates

* Add team Id to hash url (#2803)

* Fix missing tabs - Embed (#2804)

* Fix missing tabs

* Fix Eslint error

* Fix Eslint errors

* Add import statement (#2812)

* Add import statement

* Update apps/docs/next.config.js

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

* Show success page if booking was deleted on calendar (#2808)

* Add exception to 410

* Fix type error

* Add GoogelCalError type

* only show invite link for app.cal.dev (#2807)

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

* fix: update eslint config to test .ts and .js separately (#2805)

* fix: update eslint config

* fix: update ts ignore

* fix: update eslint config

* Update TeamAvailabilityScreen.tsx

* Type fixes

* Update useIntercom.ts

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

* fix: sync api to latest commit (#2810)

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Embed React improvements (#2782)

* Add off support. Add getApi export.

* Add publish command

* Add embed-snippet in prod deps

* Update README

* Update package.json

Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Consolidates test-results

* Type fixes

* Abstracts minimal booking select

* Type fixes

* Update listBookings.ts

* Update common.json

* Update bookingReminder.ts

* Consolidates isOutOfBounds

* Update webhookResponse-chromium.txt

* Update TableActions.tsx

* Type fixes

* Update BookingPage.tsx

* Update webhookResponse-chromium.txt

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: iamkun <kunhello@outlook.com>
Co-authored-by: Agusti Fernandez Pardo <me@agusti.me>
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Update check-types.yml

* adding organizer as attendee to google calendar events (#2779)

* Fix auto-select and close of dropdown (#2819)

* fixes dynamic color and typefix for tfunction after react upgrade (#2821)

* New Crowdin translations by Github Action (#2791)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>

* Relocates admin to console

# Conflicts:
#	apps/admin

* Relocates admin to console

* Fix login submit (#2849)

* fix: long string overflowing calendar div (#2842)

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>

* Adding labels (#2783)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Wrong username is identified if query params are present and user doesn't exist (#2838)

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>

* Meet/Zoom Email Clarification (#2828)

* Add clarificaiton to email

* Update apps/web/lib/emails/templates/organizer-scheduled-email.ts

* Add to attendee scheduled email

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Request the inclusion Assuncion Time Zone (#2840)

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>

* Fix UI of dialog (#2788)

* removed large mandatory height and scroll

* added z index using css

* cleanup

* fixed TS errors

* extract dialog out of dropdown

* Adds custom loading text to confirmation dialog

* rename update

* utilizing mutation loading state

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Hotfix: Fixing Security Issues (#2848)

* Fixing Privilege Escalation

* Fixing critical obj ref in availability

* Fixing reschedule security issue

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Merge production to main

* Update vital.json

* Update vital.json

* Fix login page CTA disabled state (#2832)

* fix(ui/login): better disabled state for login CTA

The fix involves tracking the form submission with a dedicated state `submitInProgress` with React.
`formState` from `react-hook-form` does not take time taken for the network call into account. For example,
if the api takes 5 seconds to complete, we would expect the `formState.isSubmitting` to be true for `5`
seconds. But, surprisingly this is not the case and `formState` from `react-hook-form` resolves
immediately after it makes a successful connection to the endpoint.

A dedicated state (with `useState`) is introduced that is enabled when the user clicks on the login CTA, and disabled when the api call is resolved, either successfully or with an error.

* Update login.tsx

* Update login.tsx

* Fixes isSubmitting state

Co-authored-by: zomars <zomars@me.com>

* Playwright binaries shouldn't be on deps

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: GitStart <1501599+gitstart@users.noreply.github.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Arthur Cruz <42497300+arthur1041@users.noreply.github.com>
Co-authored-by: Mitchell Moore <47459168+Mitchell-Moore@users.noreply.github.com>
Co-authored-by: Agusti Fernandez Pardo <me@agusti.me>
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: andreaestefania12 <19562383+andreaestefania12@users.noreply.github.com>
Co-authored-by: Miguel Nieto A <39246879+miguelnietoa@users.noreply.github.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Hashen <37979557+Hashen110@users.noreply.github.com>
Co-authored-by: buschco <colin@busch.dev>
Co-authored-by: iamkun <kunhello@outlook.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Ankit Gordhandas <agordhandas@gmail.com>
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Ken Miller <kemiller@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
Co-authored-by: Arun Kumar <palerdot@users.noreply.github.com>
This commit is contained in:
Omar López 2022-05-23 11:42:29 -06:00 committed by GitHub
parent afafd45223
commit 81d33b51bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
188 changed files with 2996 additions and 21971 deletions

View File

@ -1,6 +1,6 @@
name: Check types
on:
pull_request:
pull_request_target:
branches:
- main
jobs:
@ -19,8 +19,9 @@ jobs:
fetch-depth: 0
- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: yarn
cache: "yarn"
- run: yarn --frozen-lockfile
- run: yarn type-check

View File

@ -43,6 +43,8 @@ jobs:
# MS_GRAPH_CLIENT_SECRET: xxx
# ZOOM_CLIENT_ID: xxx
# ZOOM_CLIENT_SECRET: xxx
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
services:
postgres:
image: postgres:12.1
@ -60,21 +62,11 @@ jobs:
fetch-depth: 2
- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
# cache: "yarn"
# cache-dependency-path: yarn.lock
cache: "yarn"
- name: Turbo Cache
id: turbo-cache
uses: actions/cache@v2
with:
path: .turbo
key: turbo-${{ github.job }}-${{ github.event.pull_request.head.ref }}-${{ github.event.pull_request.head.sha }}
restore-keys: |
turbo-${{ github.job }}-${{ github.event.pull_request.head.ref }}-
- run: yarn
- name: Cache playwright binaries
uses: actions/cache@v2
id: playwright-cache
@ -82,22 +74,18 @@ jobs:
path: |
~/Library/Caches/ms-playwright
~/.cache/ms-playwright
**/node_modules/playwright
${{ github.workspace }}/node_modules/playwright
key: cache-playwright-${{ hashFiles('**/yarn.lock') }}
restore-keys: cache-playwright-
- run: yarn --frozen-lockfile
- name: Install playwright deps
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
- run: yarn test-e2e
- name: Upload videos
- name: Upload test results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: videos
path: |
test-results
playwright/screenshots
playwright/videos
playwright/results
playwright/reports
name: test-results
path: test-results

View File

@ -18,16 +18,15 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 2
- name: Use Node.js 14.x
uses: actions/setup-node@v2
- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
# cache: "yarn"
# cache-dependency-path: yarn.lock
cache: "yarn"
- name: Install deps
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn
run: yarn --frozen-lockfile
- name: Lint
run: yarn lint:report

6
.gitmodules vendored
View File

@ -1,6 +1,6 @@
[submodule "apps/admin"]
path = apps/admin
url = https://github.com/calcom/admin.git
[submodule "apps/console"]
path = apps/console
url = https://github.com/calcom/console.git
branch = main
[submodule "apps/api"]
path = apps/api

70
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,70 @@
{
"version": "2.0.0",
"presentation": {
"echo": false,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true
},
"tasks": [
{
"label": "Launch Cal.com Development terminals",
"dependsOn": [
"Web App(3000)",
"Website(3001)",
"Embed Core(3100)",
"Embed React(3101)",
"Prisma Studio(5555)"
],
// Mark as the default build task so cmd/ctrl+shift+b will create them
"group": {
"kind": "build",
"isDefault": true
},
// Try start the task on folder open
"runOptions": {
"runOn": "folderOpen"
}
},
{
// The name that shows up in terminal tab
"label": "Web App(3000)",
// The task will launch a shell
"type": "shell",
"command": "yarn dev",
// Set the shell type
// Mark as a background task to avoid the spinner animation on the terminal tab
"isBackground": false,
"problemMatcher": []
},
{
"label": "Website(3001)",
"type": "shell",
"command": "cd apps/website && yarn dev",
"isBackground": false,
"problemMatcher": []
},
{
"label": "Embed Core(3100)",
"type": "shell",
"command": "cd packages/embeds/embed-core && yarn dev",
"isBackground": false,
"problemMatcher": []
},
{
"label": "Embed React(3101)",
"type": "shell",
"command": "cd packages/embeds/embed-react && yarn dev",
"isBackground": false,
"problemMatcher": []
},
{
"label": "Prisma Studio(5555)",
"type": "shell",
"command": "yarn db-studio",
"isBackground": false,
"problemMatcher": []
}
]
}

View File

@ -436,4 +436,4 @@ Special thanks to these amazing projects which help power Cal.com:
<a href="https://jitsu.com/?utm_source=cal.com-gihub"><img height="40px" src="https://jitsu.com/img/powered-by-jitsu.png?gh=true" alt="Jitsu.com"></a>
Cal.com is an [open startup](https://jitsu.com) and [Jitsu](https://github.com/jitsucom/jitsu) (an open-source Segment alternative) helps us to track most of the usage metrics.
Cal.com is an [open startup](https://cal.com/open) and [Jitsu](https://github.com/jitsucom/jitsu) (an open-source Segment alternative) helps us to track most of the usage metrics.

@ -1 +0,0 @@
Subproject commit 943cd10de1f6661273d2ec18acdaa93118852714

@ -1 +1 @@
Subproject commit be2d4338ee1023a2d2862978ccf91554d47ff51f
Subproject commit a7889b34368eb37981b5c78953315a6ed5fc97cd

1
apps/console Submodule

@ -0,0 +1 @@
Subproject commit 67476f0e24871730e4a7b06da99ee18d4f5179ce

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const withNextra = require("nextra")({
theme: "nextra-theme-docs",
themeConfig: "./theme.config.js",

View File

@ -16,14 +16,14 @@
"license": "MIT",
"dependencies": {
"iframe-resizer-react": "^1.1.0",
"next": "^12.1.0",
"next": "^12.1.6",
"nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.1.0",
"react-dom": "^18.1.0"
},
"devDependencies": {
"@calcom/config": "*",
"eslint": "^8.10.0"
"eslint": "^8.15.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -10,16 +10,16 @@
"dependencies": {
"highlight.js": "^11.5.1",
"isarray": "2.0.5",
"next": "12.1.5",
"next": "12.1.6",
"openapi-snippet": "^0.13.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"swagger-ui-react": "4.10.3"
"react": "18.1.0",
"react-dom": "18.1.0",
"swagger-ui-react": "4.11.1"
},
"devDependencies": {
"@types/node": "17.0.27",
"@types/react": "17.0.43",
"@types/react-dom": "17.0.14",
"typescript": "4.6.3"
"@types/node": "14.17.6",
"@types/react": "18.0.9",
"@types/react-dom": "18.0.4",
"typescript": "4.6.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,18 @@
import { CodeIcon, EyeIcon, SunIcon, ChevronRightIcon, ArrowLeftIcon } from "@heroicons/react/solid";
import { ArrowLeftIcon, ChevronRightIcon, CodeIcon, EyeIcon, SunIcon } from "@heroicons/react/solid";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import classNames from "classnames";
import { useRouter } from "next/router";
import { useRef, useState } from "react";
import { components, ControlProps, SingleValue } from "react-select";
import { components, ControlProps } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { EventType } from "@calcom/prisma/client";
import { Button, Switch } from "@calcom/ui";
import { Dialog, DialogContent, DialogClose } from "@calcom/ui/Dialog";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { WEBAPP_URL, EMBED_LIB_URL } from "@lib/config/constants";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
import { trpc } from "@lib/trpc";
import NavTabs from "@components/NavTabs";
@ -241,7 +241,10 @@ const EmbedNavBar = () => {
return <NavTabs data-testid="embed-tabs" tabs={tabs} linkProps={{ shallow: true }} />;
};
const ThemeSelectControl = ({ children, ...props }: ControlProps<any, false>) => {
const ThemeSelectControl = ({
children,
...props
}: ControlProps<{ value: string; label: string }, false>) => {
return (
<components.Control {...props}>
<SunIcon className="h-[32px] w-[32px] text-gray-500" />
@ -381,7 +384,7 @@ Cal("inline", {
});
${getEmbedUIInstructionString().trim()}`;
} else if (embedType === "floating-popup") {
let floatingButtonArg = {
const floatingButtonArg = {
calLink,
...previewState.floatingPopup,
};
@ -418,7 +421,7 @@ ${getEmbedUIInstructionString().trim()}`;
});
};
const previewInstruction = (instruction: { name: string; arg: any }) => {
const previewInstruction = (instruction: { name: string; arg: unknown }) => {
iframeRef.current?.contentWindow?.postMessage(
{
mode: "cal:preview",
@ -544,7 +547,7 @@ ${getEmbedUIInstructionString().trim()}`;
value={previewState.inline.width}
onChange={(e) => {
setPreviewState((previewState) => {
let width = e.target.value || "100%";
const width = e.target.value || "100%";
return {
...previewState,
@ -759,11 +762,11 @@ ${getEmbedUIInstructionString().trim()}`;
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
//@ts-ignore - How to support dynamic palette names?
addToPalette({
[palette.name]: color,
[palette.name as keyof typeof previewState["palette"]]: color,
});
}}></ColorPicker>
}}
/>
</div>
</Label>
))}

View File

@ -2,7 +2,7 @@ import { FormEvent, useCallback, useEffect, useState } from "react";
import Cropper from "react-easy-crop";
import Button from "@calcom/ui/Button";
import { DialogClose, DialogTrigger, Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
import { Area, getCroppedImg } from "@lib/cropImage";
import { useFileReader } from "@lib/hooks/useFileReader";
@ -71,15 +71,15 @@ export default function ImageUploader({
...props
}: ImageUploaderProps) {
const { t } = useLocale();
const [imageSrc, setImageSrc] = useState<string | null>();
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>();
const [imageSrc, setImageSrc] = useState<string | null>(null);
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
const [{ result }, setFile] = useFileReader({
method: "readAsDataURL",
});
useEffect(() => {
setImageSrc(props.imageSrc);
if (props.imageSrc) setImageSrc(props.imageSrc);
}, [props.imageSrc]);
const onInputFile = (e: FileEvent<HTMLInputElement>) => {
@ -90,8 +90,9 @@ export default function ImageUploader({
};
const showCroppedImage = useCallback(
async (croppedAreaPixels) => {
async (croppedAreaPixels: Area | null) => {
try {
if (!croppedAreaPixels) return;
const croppedImage = await getCroppedImg(
result as string /* result is always string when using readAsDataUrl */,
croppedAreaPixels

View File

@ -1,9 +1,11 @@
import { AdminRequired } from "components/ui/AdminRequired";
import noop from "lodash/noop";
import Link, { LinkProps } from "next/link";
import { useRouter } from "next/router";
import React, { ElementType, FC, Fragment, MouseEventHandler } from "react";
import { FC, Fragment, MouseEventHandler } from "react";
import classNames from "@lib/classNames";
import { SVGComponent } from "@lib/types/SVGComponent";
export interface NavTabProps {
tabs: {
@ -12,7 +14,7 @@ export interface NavTabProps {
href?: string;
/** If you want to change query param tabName as per current tab */
tabName?: string;
icon?: ElementType;
icon?: SVGComponent;
adminRequired?: boolean;
}[];
linkProps?: Omit<LinkProps, "href">;
@ -27,11 +29,11 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
aria-label="Tabs"
{...props}>
{tabs.map((tab) => {
let href: string;
let isCurrent;
if ((tab.tabName && tab.href) || (!tab.tabName && !tab.href)) {
throw new Error("Use either tabName or href");
}
let href = "";
let isCurrent;
if (tab.href) {
href = tab.href;
isCurrent = router.asPath === tab.href;
@ -39,6 +41,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
href = "";
isCurrent = router.query.tabName === tab.tabName;
}
const onClick: MouseEventHandler = tab.tabName
? (e) => {
e.preventDefault();
@ -49,12 +52,13 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
},
});
}
: () => {};
: noop;
const Component = tab.adminRequired ? AdminRequired : Fragment;
return (
<Component key={tab.name}>
<Link key={tab.name} href={href!} {...linkProps}>
<Link key={tab.name} href={href} {...linkProps}>
<a
onClick={onClick}
className={classNames(

View File

@ -3,7 +3,7 @@ import React from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import NavTabs, { NavTabProps } from "./NavTabs";
import NavTabs from "./NavTabs";
export default function SettingsShell({ children }: { children: React.ReactNode }) {
const { t } = useLocale();

View File

@ -206,6 +206,7 @@ const Layout = ({
<Fragment key={item.name}>
<Link href={item.href}>
<a
aria-label={item.name}
className={classNames(
item.current
? "bg-neutral-100 text-neutral-900"

View File

@ -1,4 +1,3 @@
import { StarIcon } from "@heroicons/react/solid";
import Link from "next/link";
import Button from "@calcom/ui/Button";

View File

@ -1,6 +1,6 @@
import React from "react";
import { SkeletonAvatar, SkeletonText } from "@calcom/ui";
import { SkeletonText } from "@calcom/ui";
import { ShellSubHeading } from "@components/Shell";

View File

@ -4,7 +4,7 @@ import "@glidejs/glide/dist/css/glide.theme.min.css";
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/solid";
import { useEffect, useRef } from "react";
const Slider = <T extends unknown>({
const Slider = <T extends string | unknown>({
title = "",
className = "",
items,

View File

@ -6,7 +6,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { Button } from "@calcom/ui";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
import { Form, TextField } from "@calcom/ui/form/fields";
import { Form } from "@calcom/ui/form/fields";
import { HttpError } from "@lib/core/http/error";
import { trpc } from "@lib/trpc";

View File

@ -6,7 +6,7 @@ import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { GroupBase, Props, SingleValue } from "react-select";
import { GroupBase, Props } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";

View File

@ -16,7 +16,7 @@ export function ScheduleListItem({
isDeleting = false,
}: {
schedule: inferQueryOutput<"viewer.availability.list">["schedules"][number];
deleteFunction: Function;
deleteFunction: ({ scheduleId }: { scheduleId: number }) => void;
isDeleting: boolean;
}) {
const { t, i18n } = useLocale();

View File

@ -1,8 +1,15 @@
import { BanIcon, CheckIcon, ClockIcon, XIcon, PencilAltIcon } from "@heroicons/react/outline";
import { PaperAirplaneIcon } from "@heroicons/react/outline";
import {
BanIcon,
CheckIcon,
ClockIcon,
PaperAirplaneIcon,
PencilAltIcon,
XIcon,
} from "@heroicons/react/outline";
import { RefreshIcon } from "@heroicons/react/solid";
import { BookingStatus } from "@prisma/client";
import dayjs from "dayjs";
import { useRouter } from "next/router";
import { useState } from "react";
import { useMutation } from "react-query";
import { Frequency as RRuleFrequency } from "rrule";
@ -17,7 +24,7 @@ import { TextArea } from "@calcom/ui/form/fields";
import { HttpError } from "@lib/core/http/error";
import useMeQuery from "@lib/hooks/useMeQuery";
import { parseRecurringDates } from "@lib/parseDate";
import { inferQueryOutput, trpc, inferQueryInput } from "@lib/trpc";
import { inferQueryInput, inferQueryOutput, trpc } from "@lib/trpc";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
import TableActions, { ActionType } from "@components/ui/TableActions";
@ -37,6 +44,7 @@ function BookingListItem(booking: BookingItemProps) {
const user = query.data;
const { t, i18n } = useLocale();
const utils = trpc.useContext();
const router = useRouter();
const [rejectionReason, setRejectionReason] = useState<string>("");
const [rejectionDialogIsOpen, setRejectionDialogIsOpen] = useState(false);
const mutation = useMutation(
@ -81,7 +89,10 @@ function BookingListItem(booking: BookingItemProps) {
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
? t("reject_all")
: t("reject"),
onClick: () => setRejectionDialogIsOpen(true),
onClick: (e) => {
e.stopPropagation();
setRejectionDialogIsOpen(true);
},
icon: BanIcon,
disabled: mutation.isLoading,
},
@ -91,7 +102,10 @@ function BookingListItem(booking: BookingItemProps) {
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
? t("confirm_all")
: t("confirm"),
onClick: () => mutation.mutate(true),
onClick: (e) => {
e.stopPropagation();
mutation.mutate(true);
},
icon: CheckIcon,
disabled: mutation.isLoading,
color: "primary",
@ -120,7 +134,10 @@ function BookingListItem(booking: BookingItemProps) {
id: "reschedule_request",
icon: ClockIcon,
label: t("send_reschedule_request"),
onClick: () => setIsOpenRescheduleDialog(true),
onClick: (e) => {
e.stopPropagation();
setIsOpenRescheduleDialog(true);
},
},
],
},
@ -150,6 +167,7 @@ function BookingListItem(booking: BookingItemProps) {
i18n
);
}
return (
<>
<RescheduleDialog
@ -191,7 +209,30 @@ function BookingListItem(booking: BookingItemProps) {
</DialogContent>
</Dialog>
<tr className="flex">
<tr
className="flex cursor-pointer hover:bg-neutral-50"
onClick={() =>
router.push({
pathname: "/success",
query: {
date: booking.startTime,
type: booking.eventType.id,
eventSlug: booking.eventType.slug,
user: user?.username || "",
name: booking.attendees[0].name,
email: booking.attendees[0].email,
location: booking.location
? booking.location.includes("integration")
? (t("web_conferencing_details_to_follow") as string)
: booking.location
: "",
eventName: booking.eventType.eventName || "",
bookingId: booking.id,
recur: booking.recurringEventId,
reschedule: booking.confirmed,
},
})
}>
<td className="hidden whitespace-nowrap py-4 align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:w-56">
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
<div className="text-sm text-gray-500">
@ -264,9 +305,12 @@ function BookingListItem(booking: BookingItemProps) {
)}
{booking.attendees.length !== 0 && (
<div className="text-sm text-gray-900 hover:text-blue-500">
<a href={"mailto:" + booking.attendees[0].email}>{booking.attendees[0].email}</a>
</div>
<a
className="text-sm text-gray-900 hover:text-blue-500"
href={"mailto:" + booking.attendees[0].email}
onClick={(e) => e.stopPropagation()}>
{booking.attendees[0].email}
</a>
)}
{isCancelled && booking.rescheduled && (
<div className="mt-2 inline-block text-left text-sm md:hidden">

View File

@ -0,0 +1,119 @@
import { XIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/Button";
import useTheme from "@lib/hooks/useTheme";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
type Props = {
booking: {
title?: string;
uid?: string;
};
profile: {
name: string | null;
slug: string | null;
};
team?: string | null;
setIsCancellationMode: (value: boolean) => void;
theme: string | null;
};
export default function CancelBooking(props: Props) {
const [cancellationReason, setCancellationReason] = useState<string>("");
const { t } = useLocale();
const router = useRouter();
const { booking, profile, team } = props;
const [loading, setLoading] = useState(false);
const telemetry = useTelemetry();
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
const { isReady, Theme } = useTheme(props.theme);
if (isReady) {
return (
<>
<Theme />
{error && (
<div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<XIcon className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{error}
</h3>
</div>
</div>
)}
{!error && (
<div className="mt-5 sm:mt-6">
<label className="text-bookingdark font-medium dark:text-white">{t("cancellation_reason")}</label>
<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 "
rows={3}
/>
<div className="flex rtl:space-x-reverse">
<div className="w-full">
<Button color="secondary" onClick={() => router.push("/reschedule/" + booking?.uid)}>
{t("reschedule_this")}
</Button>
</div>
<div className="w-full space-x-2 text-right">
<Button color="secondary" onClick={() => props.setIsCancellationMode(false)}>
{t("nevermind")}
</Button>
<Button
data-testid="cancel"
onClick={async () => {
setLoading(true);
const payload = {
uid: booking?.uid,
reason: cancellationReason,
};
telemetry.withJitsu((jitsu) =>
jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters())
);
const res = await fetch("/api/cancel", {
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
method: "DELETE",
});
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}`
);
} else {
setLoading(false);
setError(
`${t("error_with_status_code_occured", { status: res.status })} ${t(
"please_try_again"
)}`
);
}
}}
loading={loading}>
{t("cancel_event")}
</Button>
</div>
</div>
</div>
)}
</>
);
}
return <></>;
}

View File

@ -1,7 +1,7 @@
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
import { EventType, PeriodType } from "@prisma/client";
import { PeriodType } from "@prisma/client";
import dayjs, { Dayjs } from "dayjs";
import dayjsBusinessTime from "dayjs-business-time";
import dayjsBusinessTime from "dayjs-business-days2";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { memoize } from "lodash";
@ -14,6 +14,7 @@ import classNames from "@lib/classNames";
import { timeZone } from "@lib/clock";
import { weekdayNames } from "@lib/core/i18n/weekday";
import { doWorkAsync } from "@lib/doWorkAsync";
import isOutOfBounds from "@lib/isOutOfBounds";
import getSlots from "@lib/slots";
import { WorkingHours } from "@lib/types/schedule";
@ -37,41 +38,6 @@ type DatePickerProps = {
minimumBookingNotice: number;
};
function isOutOfBounds(
time: dayjs.ConfigType,
{
periodType,
periodDays,
periodCountCalendarDays,
periodStartDate,
periodEndDate,
}: Pick<
EventType,
"periodType" | "periodDays" | "periodCountCalendarDays" | "periodStartDate" | "periodEndDate"
>
) {
const date = dayjs(time);
switch (periodType) {
case PeriodType.ROLLING: {
const periodRollingEndDay = periodCountCalendarDays
? dayjs().utcOffset(date.utcOffset()).add(periodDays!, "days").endOf("day")
: dayjs().utcOffset(date.utcOffset()).addBusinessTime(periodDays!, "days").endOf("day");
return date.endOf("day").isAfter(periodRollingEndDay);
}
case PeriodType.RANGE: {
const periodRangeStartDay = dayjs(periodStartDate).utcOffset(date.utcOffset()).endOf("day");
const periodRangeEndDay = dayjs(periodEndDate).utcOffset(date.utcOffset()).endOf("day");
return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay);
}
case PeriodType.UNLIMITED:
default:
return false;
}
}
function DatePicker({
weekStart,
onDatePicked,
@ -94,7 +60,7 @@ function DatePicker({
const [isFirstMonth, setIsFirstMonth] = useState<boolean>(false);
const [daysFromState, setDays] = useState<
| {
disabled: Boolean;
disabled: boolean;
date: number;
}[]
| null
@ -191,7 +157,7 @@ function DatePicker({
name: "DatePicker",
length: daysInMonth,
callback: (i: number) => {
let day = i + 1;
const day = i + 1;
days[daysInitialOffset + i] = {
disabled: isDisabledMemoized(day, {
browsingDate,

View File

@ -1,10 +1,11 @@
import { FC, useEffect, useState } from "react";
import TimezoneSelect, { ITimezoneOption } from "react-timezone-select";
import Switch from "@calcom/ui/Switch";
import { useLocale } from "@lib/hooks/useLocale";
import TimezoneSelect, { ITimezoneOption } from "@components/ui/form/TimezoneSelect";
import { is24h, timeZone } from "../../lib/clock";
type Props = {

View File

@ -25,7 +25,6 @@ import {
useIsEmbed,
useIsBackgroundTransparent,
sdkActionManager,
useEmbedType,
useEmbedNonStylesConfig,
} from "@calcom/embed-core";
import classNames from "@calcom/lib/classNames";
@ -68,7 +67,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
let isBackgroundTransparent = useIsBackgroundTransparent();
const isBackgroundTransparent = useIsBackgroundTransparent();
useExposePlanGlobally(plan);
useEffect(() => {
if (eventType.metadata.smartContractAddress) {
@ -213,9 +212,11 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
truncateAfter={5}
/>
<div className="mt-4">
<p className="text-sm font-medium text-black dark:text-white">{profile.name}</p>
<p className="break-words text-sm font-medium text-black dark:text-white">
{profile.name}
</p>
<div className="mt-2 gap-2 dark:text-gray-100">
<h1 className="text-bookingdark mb-4 text-xl font-semibold dark:text-white">
<h1 className="text-bookingdark mb-4 break-words text-xl font-semibold dark:text-white">
{eventType.title}
</h1>
{eventType?.description && (
@ -264,7 +265,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
<div className="px-4 sm:flex sm:p-4 sm:py-5">
<div
className={
"hidden pr-8 sm:border-r sm:dark:border-gray-700 md:flex md:flex-col " +
"hidden overflow-hidden pr-8 sm:border-r sm:dark:border-gray-700 md:flex md:flex-col " +
(selectedDate ? "sm:w-1/3" : recurringEventCount ? "sm:w-2/3" : "sm:w-1/2")
}>
<AvatarGroup
@ -284,8 +285,10 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
size={10}
truncateAfter={3}
/>
<h2 className="mt-3 font-medium text-gray-500 dark:text-gray-300">{profile.name}</h2>
<h1 className="font-cal mb-4 text-xl font-semibold text-gray-900 dark:text-white">
<h2 className="mt-3 break-words font-medium text-gray-500 dark:text-gray-300">
{profile.name}
</h2>
<h1 className="font-cal mb-4 break-words text-xl font-semibold text-gray-900 dark:text-white">
{eventType.title}
</h1>
{eventType?.description && (

View File

@ -52,6 +52,15 @@ import { BookPageProps } from "../../../pages/[user]/book";
import { HashLinkPageProps } from "../../../pages/d/[link]/book";
import { TeamBookingPageProps } from "../../../pages/team/[slug]/book";
declare global {
// eslint-disable-next-line no-var
var web3: {
currentProvider: {
selectedAddress: string;
};
};
}
/** These are like 40kb that not every user needs */
const PhoneInput = dynamic(
() => import("@components/ui/form/PhoneInput")
@ -68,7 +77,7 @@ type BookingFormValues = {
phone?: string;
hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers
customInputs?: {
[key: string]: string;
[key: string]: string | boolean;
};
};
@ -107,7 +116,6 @@ const BookingPage = ({
const eventOwner = eventType.users[0];
if (!contracts[(eventType.metadata.smartContractAddress || null) as number])
/* @ts-ignore */
router.replace(`/${eventOwner.username}`);
}
}, [contracts, eventType.metadata.smartContractAddress, eventType.users, router]);
@ -208,7 +216,7 @@ const BookingPage = ({
}, [router.query.guest]);
const locationInfo = (type: LocationType) => locations.find((location) => location.type === type);
const loggedInIsOwner = eventType?.users[0]?.name === session?.user?.name;
const loggedInIsOwner = eventType?.users[0]?.id === session?.user?.id;
const guestListEmails = !isDynamicGroupBooking
? booking?.attendees.slice(1).map((attendee) => attendee.email)
: [];
@ -236,11 +244,22 @@ const BookingPage = ({
if (!primaryAttendee) {
return {};
}
const customInputType = booking.customInputs;
return {
name: primaryAttendee.name || "",
email: primaryAttendee.email || "",
guests: guestListEmails,
notes: booking.description || "",
customInputs: eventType.customInputs.reduce(
(customInputs, input) => ({
...customInputs,
[input.id]: booking.customInputs
? booking.customInputs[input.label as keyof typeof customInputType]
: "",
}),
{}
),
};
};
@ -331,7 +350,6 @@ const BookingPage = ({
let web3Details: Record<"userWallet" | "userSignature", string> | undefined;
if (eventTypeDetail.metadata.smartContractAddress) {
web3Details = {
// @ts-ignore
userWallet: window.web3.currentProvider.selectedAddress,
userSignature: contracts[(eventTypeDetail.metadata.smartContractAddress || null) as number],
};
@ -359,8 +377,8 @@ const BookingPage = ({
),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))!.label,
value: booking.customInputs![inputId],
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
})),
hasHashedBookingLink,
hashedLink,
@ -383,8 +401,8 @@ const BookingPage = ({
),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))!.label,
value: booking.customInputs![inputId],
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
})),
hasHashedBookingLink,
hashedLink,
@ -393,6 +411,9 @@ const BookingPage = ({
};
const disableInput = !!rescheduleUid;
const disabledExceptForOwner = disableInput && !loggedInIsOwner;
const inputClassName =
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 disabled:dark:text-gray-500 sm:text-sm";
return (
<div>
@ -534,10 +555,7 @@ const BookingPage = ({
name="name"
id="name"
required
className={classNames(
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
)}
className={inputClassName}
placeholder={t("example_name")}
disabled={disableInput}
/>
@ -554,8 +572,7 @@ const BookingPage = ({
{...bookingForm.register("email")}
required
className={classNames(
"focus:border-brand block w-full rounded-sm shadow-sm focus:ring-black dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
disableInput ? "bg-gray-200 dark:text-gray-500" : "",
inputClassName,
bookingForm.formState.errors.email
? "border-red-700 focus:ring-red-700"
: " border-gray-300 dark:border-gray-900"
@ -630,12 +647,9 @@ const BookingPage = ({
})}
id={"custom_" + input.id}
rows={3}
className={classNames(
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
)}
className={inputClassName}
placeholder={input.placeholder}
disabled={disableInput}
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.TEXT && (
@ -645,9 +659,9 @@ const BookingPage = ({
required: input.required,
})}
id={"custom_" + input.id}
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm"
className={inputClassName}
placeholder={input.placeholder}
disabled={disableInput}
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.NUMBER && (
@ -657,8 +671,9 @@ const BookingPage = ({
required: input.required,
})}
id={"custom_" + input.id}
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm"
className={inputClassName}
placeholder=""
disabled={disabledExceptForOwner}
/>
)}
{input.type === EventTypeCustomInputType.BOOL && (
@ -669,8 +684,9 @@ const BookingPage = ({
required: input.required,
})}
id={"custom_" + input.id}
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black disabled:bg-gray-200 ltr:mr-2 rtl:ml-2 disabled:dark:text-gray-500"
placeholder=""
disabled={disabledExceptForOwner}
/>
<label
htmlFor={"custom_" + input.id}
@ -757,12 +773,9 @@ const BookingPage = ({
id="notes"
name="notes"
rows={3}
className={classNames(
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
)}
className={inputClassName}
placeholder={t("share_additional_notes")}
disabled={disableInput}
disabled={disabledExceptForOwner}
/>
</div>
<div className="flex items-start space-x-2 rtl:space-x-reverse">

View File

@ -12,6 +12,7 @@ export type ConfirmationDialogContentProps = {
confirmBtnText?: string;
cancelBtnText?: string;
isLoading?: boolean;
loadingText?: string;
onConfirm?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
title: string;
variety?: "danger" | "warning" | "success";
@ -25,6 +26,7 @@ export default function ConfirmationDialogContent(props: PropsWithChildren<Confi
confirmBtn = null,
confirmBtnText = t("confirm"),
cancelBtnText = t("cancel"),
loadingText = t("loading"),
isLoading = false,
onConfirm,
children,
@ -63,7 +65,7 @@ export default function ConfirmationDialogContent(props: PropsWithChildren<Confi
<DialogClose disabled={isLoading} onClick={onConfirm} asChild>
{confirmBtn || (
<Button color="primary" loading={isLoading}>
{isLoading ? t("loading") : confirmBtnText}
{isLoading ? loadingText : confirmBtnText}
</Button>
)}
</DialogClose>

View File

@ -1,15 +1,13 @@
import { ExclamationIcon } from "@heroicons/react/outline";
import { CheckIcon } from "@heroicons/react/solid";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import React, { PropsWithChildren, ReactNode } from "react";
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 { useLocale } from "@lib/hooks/useLocale";
export type DeleteStripeDialogContentProps = {
confirmBtn?: ReactNode;
cancelAllBookingsBtnText?: string;
removeBtnText?: string;
cancelBtnText?: string;
@ -24,7 +22,6 @@ export default function DeleteStripeDialogContent(props: PropsWithChildren<Delet
const {
title,
variety,
confirmBtn = null,
cancelAllBookingsBtnText,
removeBtnText,
cancelBtnText = t("cancel"),

View File

@ -1,4 +1,4 @@
import { ClockIcon, XIcon } from "@heroicons/react/outline";
import { ClockIcon } from "@heroicons/react/outline";
import { RescheduleResponse } from "pages/api/book/request-reschedule";
import React, { useState, Dispatch, SetStateAction } from "react";
import { useMutation } from "react-query";

View File

@ -1,4 +1,5 @@
import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible";
import type { FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { UseFormReturn } from "react-hook-form";
import { Frequency as RRuleFrequency } from "rrule";
@ -11,9 +12,9 @@ import Select from "@components/ui/form/Select";
type RecurringEventControllerProps = {
recurringEvent: RecurringEvent;
formMethods: UseFormReturn<any, any>;
formMethods: UseFormReturn<FormValues>;
paymentEnabled: boolean;
onRecurringEventDefined: Function;
onRecurringEventDefined: (value: boolean) => void;
};
export default function RecurringEventController({
@ -43,9 +44,7 @@ export default function RecurringEventController({
return (
<div className="block items-start sm:flex">
<div className="min-w-48 mb-4 sm:mb-0">
<label htmlFor="recurringEvent" className="flex text-sm font-medium text-neutral-700">
{t("recurring_event")}
</label>
<span className="flex text-sm font-medium text-neutral-700">{t("recurring_event")}</span>
</div>
<div className={!paymentEnabled ? "w-full" : ""}>
{paymentEnabled ? (
@ -78,10 +77,13 @@ export default function RecurringEventController({
className="text-primary-600 h-4 w-4 rounded border-gray-300"
defaultChecked={recurringEventDefined}
data-testid="recurring-event-check"
id="recurringEvent"
/>
</div>
<div className="text-sm ltr:ml-3 rtl:mr-3">
<p className="text-neutral-900">{t("recurring_event_description")}</p>
<label htmlFor="recurringEvent" className="text-neutral-900">
{t("recurring_event_description")}
</label>
</div>
</div>
<Collapsible

View File

@ -118,7 +118,6 @@ export default function TeamSettingsRightSidebar(props: { team: TeamWithMembers;
)}
</div>
{props.team?.id && props.role !== MembershipRole.MEMBER && (
// eslint-disable-next-line @next/next/link-passhref
<Link href={`/settings/teams/${props.team.id}/availability`}>
<div className="mt-5 hidden space-y-1 sm:block">
<LinkIconButton Icon={ClockIcon}>{"View Availability"}</LinkIconButton>

View File

@ -37,7 +37,6 @@ const Team = ({ team }: TeamPageProps) => {
);
return (
// eslint-disable-next-line @next/next/link-passhref
<Link key={member.id} href={`/${member.username}`}>
<div className={classes}>
<ArrowRightIcon

View File

@ -3,6 +3,7 @@ import { FC, Fragment } from "react";
type AdminRequiredProps = {
as?: keyof JSX.IntrinsicElements;
children?: React.ReactNode;
};
export const AdminRequired: FC<AdminRequiredProps> = ({ children, as, ...rest }) => {

View File

@ -4,9 +4,7 @@ import { Trans } from "next-i18next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Alert } from "@calcom/ui/Alert";
type Props = {};
function ImpersonatingBanner({}: Props) {
function ImpersonatingBanner() {
const { t } = useLocale();
const { data } = useSession();

View File

@ -1,7 +1,7 @@
import classNames from "classnames";
import React from "react";
import { Dialog, DialogContent, DialogFooter } from "@calcom/ui/Dialog";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
interface Props extends React.PropsWithChildren<any> {
wide?: boolean;

View File

@ -2,7 +2,7 @@ import { ChevronDownIcon, DotsHorizontalIcon } from "@heroicons/react/solid";
import React, { FC } from "react";
import Button from "@calcom/ui/Button";
import Dropdown, { DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@calcom/ui/Dropdown";
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@calcom/ui/Dropdown";
import { SVGComponent } from "@lib/types/SVGComponent";
@ -12,15 +12,27 @@ export type ActionType = {
label: string;
disabled?: boolean;
color?: "primary" | "secondary";
} & ({ href?: never; onClick: () => any } | { href?: string; onClick?: never }) & {
actions?: ActionType[];
};
} & (
| { href: string; onClick?: never; actions?: never }
| { href?: never; onClick: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void; actions?: never }
| { actions?: ActionType[]; href?: never; onClick?: never }
);
interface Props {
actions: ActionType[];
}
const DropdownActions = ({ actions, actionTrigger }: { actions: ActionType[]; actionTrigger?: any }) => {
const defaultAction = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.stopPropagation();
};
const DropdownActions = ({
actions,
actionTrigger,
}: {
actions: ActionType[];
actionTrigger?: React.ReactNode;
}) => {
return (
<Dropdown>
{!actionTrigger ? (
@ -40,7 +52,7 @@ const DropdownActions = ({ actions, actionTrigger }: { actions: ActionType[]; ac
className="w-full rounded-none font-normal"
href={action.href}
StartIcon={action.icon}
onClick={action.onClick}
onClick={action.onClick || defaultAction}
data-testid={action.id}>
{action.label}
</Button>
@ -67,7 +79,7 @@ const TableActions: FC<Props> = ({ actions }) => {
key={action.id}
data-testid={action.id}
href={action.href}
onClick={action.onClick}
onClick={action.onClick || defaultAction}
StartIcon={action.icon}
{...(action?.actions ? { EndIcon: ChevronDownIcon } : null)}
disabled={action.disabled}

View File

@ -1,36 +1,56 @@
import React, { forwardRef, InputHTMLAttributes } from "react";
import InfoBadge from "@components/ui/InfoBadge";
type Props = InputHTMLAttributes<HTMLInputElement> & {
label: React.ReactNode;
label?: React.ReactNode;
description: string;
descriptionAsLabel?: boolean;
infomationIconText?: string;
};
const CheckboxField = forwardRef<HTMLInputElement, Props>(({ label, description, ...rest }, ref) => {
return (
<div className="block items-center sm:flex">
<div className="min-w-48 mb-4 sm:mb-0">
<label htmlFor={rest.id} className="flex text-sm font-medium text-neutral-700">
{label}
</label>
</div>
<div className="w-full">
<div className="relative flex items-start">
<div className="flex h-5 items-center">
<input
{...rest}
ref={ref}
type="checkbox"
className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-gray-300"
/>
const CheckboxField = forwardRef<HTMLInputElement, Props>(
({ label, description, infomationIconText, descriptionAsLabel, ...rest }, ref) => {
return (
<div className="block items-center sm:flex">
{label && !descriptionAsLabel && (
<div className="min-w-48 mb-4 sm:mb-0">
<label htmlFor={rest.id} className="flex text-sm font-medium text-neutral-700">
{label}
</label>
</div>
<div className="text-sm ltr:ml-3 rtl:mr-3">
<p className="text-neutral-900">{description}</p>
)}
{label && descriptionAsLabel && (
<div className="min-w-48 mb-4 sm:mb-0">
<span className="flex text-sm font-medium text-neutral-700">{label}</span>
</div>
)}
<div className="w-full">
<div className="relative flex items-start">
<div className="flex h-5 items-center">
<input
{...rest}
ref={ref}
type="checkbox"
className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-gray-300"
/>
</div>
<div className="text-sm ltr:ml-3 rtl:mr-3">
{!label || descriptionAsLabel ? (
<label htmlFor={rest.id} className="text-neutral-700">
{description}
</label>
) : (
<p className="text-neutral-900">{description}</p>
)}
</div>
{infomationIconText && <InfoBadge content={infomationIconText}></InfoBadge>}
</div>
</div>
</div>
</div>
);
});
);
}
);
CheckboxField.displayName = "CheckboxField";

View File

@ -1,6 +1,11 @@
import classNames from "classnames";
import { components } from "react-select";
import BaseSelect, { ITimezone, Props as SelectProps } from "react-timezone-select";
import BaseSelect, {
allTimezones,
ITimezoneOption,
ITimezone,
Props as SelectProps,
} from "react-timezone-select";
import { InputComponent } from "@components/ui/form/Select";
@ -28,6 +33,10 @@ function TimezoneSelect({ className, ...props }: SelectProps) {
},
}),
}}
timezones={{
...allTimezones,
"America/Asuncion": "Asuncion",
}}
components={{
...components,
IndicatorSeparator: () => null,
@ -40,4 +49,4 @@ function TimezoneSelect({ className, ...props }: SelectProps) {
}
export default TimezoneSelect;
export type { ITimezone };
export type { ITimezone, ITimezoneOption };

View File

@ -36,7 +36,7 @@ type ChildrenProps = {
props: RadioAreaProps;
children?: ReactNode;
};
interface RadioAreaGroupProps extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange"> {
interface RadioAreaGroupProps extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
children: ChildrenProps | ChildrenProps[];
name?: string;
onChange?: (value: string) => void;

View File

@ -1,10 +1,17 @@
import noop from "lodash/noop";
import { createContext, ReactNode, useContext } from "react";
import { localStorage } from "@calcom/lib/webstorage";
type contractsContextType = Record<string, string>;
type contractsContextType = {
contracts: Record<string, string>;
addContract: (payload: addContractsPayload) => void;
};
const contractsContextDefaultValue: contractsContextType = {};
const contractsContextDefaultValue: contractsContextType = {
contracts: {},
addContract: noop,
};
const ContractsContext = createContext<contractsContextType>(contractsContextDefaultValue);
@ -37,10 +44,5 @@ export function ContractsProvider({ children }: Props) {
addContract,
};
return (
<>
{/* @ts-ignore */}
<ContractsContext.Provider value={value}>{children}</ContractsContext.Provider>
</>
);
return <ContractsContext.Provider value={value}>{children}</ContractsContext.Provider>;
}

View File

@ -1,7 +1,6 @@
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import React, { useState, useEffect } from "react";
import TimezoneSelect, { ITimezone } from "react-timezone-select";
import { WEBSITE_URL } from "@calcom/lib/constants";
@ -10,6 +9,7 @@ import { trpc, inferQueryOutput } from "@lib/trpc";
import Avatar from "@components/ui/Avatar";
import { DatePicker } from "@components/ui/form/DatePicker";
import Select from "@components/ui/form/Select";
import TimezoneSelect, { ITimezone } from "@components/ui/form/TimezoneSelect";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";

View File

@ -1,16 +1,16 @@
import dayjs from "dayjs";
import React, { useState, useEffect, CSSProperties } from "react";
import TimezoneSelect, { ITimezone } from "react-timezone-select";
import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeList as List } from "react-window";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { trpc, inferQueryOutput } from "@lib/trpc";
import { inferQueryOutput, trpc } from "@lib/trpc";
import Avatar from "@components/ui/Avatar";
import { DatePicker } from "@components/ui/form/DatePicker";
import Select from "@components/ui/form/Select";
import TimezoneSelect, { ITimezone } from "@components/ui/form/TimezoneSelect";
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";

View File

@ -1,20 +1,19 @@
import Router from "next/router";
import { useCallback, useMemo, useState } from "react";
import React from "react";
import React, { useCallback, useMemo, useState } from "react";
import Web3 from "web3";
import type { AbstractProvider } from "web3-core";
import { AbiItem } from "web3-utils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { Button } from "@calcom/ui/Button";
import { useLocale } from "@lib/hooks/useLocale";
import { useContracts } from "../../../contexts/contractsContext";
import genericAbi from "../../../web3/abis/abiWithGetBalance.json";
import verifyAccount, { AUTH_MESSAGE } from "../../../web3/utils/verifyAccount";
interface Window {
ethereum: any;
ethereum: AbstractProvider & { selectedAddress: string };
web3: Web3;
}
interface EvtsToVerify {
@ -40,7 +39,7 @@ const CryptoSection = (props: CryptoSectionProps) => {
const { t } = useLocale();
const connectMetamask = useCallback(async () => {
if (window.ethereum) {
if (window.ethereum && typeof window.ethereum.request === "function") {
await window.ethereum.request({ method: "eth_requestAccounts" });
window.web3 = new Web3(window.ethereum);
toggleEthEnabled(true);
@ -57,17 +56,13 @@ const CryptoSection = (props: CryptoSectionProps) => {
const contract = new window.web3.eth.Contract(genericAbi as AbiItem[], props.smartContractAddress);
const balance = await contract.methods.balanceOf(window.ethereum.selectedAddress).call();
const hasToken = balance > 0;
if (!hasToken) {
throw new Error("Specified wallet does not own any tokens belonging to this smart contract");
} else {
const account = (await window.web3.eth.getAccounts())[0];
// @ts-ignore
const signature = await window.web3.eth.personal.sign(AUTH_MESSAGE, account);
// @ts-ignore
const [account] = await window.web3.eth.getAccounts();
const signature = await window.web3.eth.personal.sign(AUTH_MESSAGE, account, "");
addContract({ address: props.smartContractAddress, signature });
await verifyAccount(signature, account);
@ -93,7 +88,7 @@ const CryptoSection = (props: CryptoSectionProps) => {
const verifyButton = useMemo(() => {
return (
<Button color="secondary" onClick={verifyWallet} type="button" id="hasToken" name="hasToken">
<Button color="secondary" onClick={verifyWallet} type="button" id="hasToken">
{
// eslint-disable-next-line @next/next/no-img-element
<img className="mr-1 h-5" src="/apps/metamask.svg" alt="MetaMask" />

View File

@ -1,7 +1,7 @@
import { FC } from "react";
import { LiveChatLoaderProvider } from "react-live-chat-loader";
const Provider: FC = ({ children }) => (
const Provider: FC<{ children: React.ReactNode }> = ({ children }) => (
<LiveChatLoaderProvider providerKey={process.env.NEXT_PUBLIC_HELPSCOUT_KEY!} provider="helpScout">
<>{children}</>
</LiveChatLoaderProvider>

View File

@ -11,6 +11,7 @@ const ImpersonationProvider = CredentialsProvider({
username: { label: "Username", type: "text " },
},
async authorize(creds, req) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore need to figure out how to correctly type this
const session = await getSession({ req });
if (session?.user.role !== "ADMIN") {

View File

@ -1,11 +1,12 @@
import { ChatAltIcon } from "@heroicons/react/solid";
import { useIntercom } from "react-use-intercom";
import { DropdownMenuItem } from "@calcom/ui/Dropdown";
import classNames from "@lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
import { useIntercom } from "./useIntercom";
export default function IntercomMenuItem() {
const { t } = useLocale();
const { boot, show } = useIntercom();

View File

@ -1,7 +1,7 @@
import { FC } from "react";
import { IntercomProvider } from "react-use-intercom";
const Provider: FC = ({ children }) => (
const Provider: FC<{ children: React.ReactNode }> = ({ children }) => (
<IntercomProvider appId={process.env.NEXT_PUBLIC_INTERCOM_APP_ID!}>{children}</IntercomProvider>
);

View File

@ -0,0 +1,12 @@
import noop from "lodash/noop";
import { useIntercom as _useIntercom } from "react-use-intercom";
export const useIntercom =
typeof window !== "undefined" && !!process.env.NEXT_PUBLIC_INTERCOM_APP_ID
? _useIntercom
: () => ({
boot: noop,
show: noop,
});
export default useIntercom;

View File

@ -4,8 +4,9 @@ import type { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";
import EventManager from "@calcom/core/EventManager";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import prisma from "@calcom/prisma";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import stripe from "@calcom/stripe/server";
import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
@ -42,16 +43,11 @@ async function handlePaymentSuccess(event: Stripe.Event) {
id: payment.bookingId,
},
select: {
title: true,
description: true,
startTime: true,
endTime: true,
...bookingMinimalSelect,
confirmed: true,
attendees: true,
location: true,
eventTypeId: true,
userId: true,
id: true,
uid: true,
paid: true,
destinationCalendar: true,
@ -113,8 +109,9 @@ async function handlePaymentSuccess(event: Stripe.Event) {
description: booking.description || undefined,
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
customInputs: isPrismaObjOrUndefined(booking.customInputs),
organizer: {
email: user.email!,
email: user.email,
name: user.name!,
timeZone: user.timeZone,
language: { translate: t, locale: user.locale ?? "en" },
@ -126,7 +123,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
if (booking.location) evt.location = booking.location;
let bookingData: Prisma.BookingUpdateInput = {
const bookingData: Prisma.BookingUpdateInput = {
paid: true,
confirmed: true,
};

View File

@ -1,6 +1,6 @@
import { SessionProvider } from "next-auth/react";
import { appWithTranslation } from "next-i18next";
import type { AppProps as NextAppProps } from "next/app";
import type { AppProps as NextAppProps, AppProps as NextJsAppProps } from "next/app";
import { ComponentProps, ReactNode, useMemo } from "react";
import DynamicHelpscoutProvider from "@ee/lib/helpscout/providerDynamic";
@ -11,7 +11,9 @@ import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry";
import { trpc } from "./trpc";
const I18nextAdapter = appWithTranslation(({ children }: { children?: ReactNode }) => <>{children}</>);
const I18nextAdapter = appWithTranslation<NextJsAppProps & { children: React.ReactNode }>(({ children }) => (
<>{children}</>
));
// Workaround for https://github.com/vercel/next.js/issues/8592
export type AppProps = NextAppProps & {

View File

@ -1,4 +1,5 @@
import { Account, IdentityProvider, Prisma, PrismaClient, User, VerificationToken } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
import { identityProviderNameMap } from "@lib/auth";
@ -45,6 +46,7 @@ export default function CalComAdapter(prismaClient: PrismaClient) {
prismaClient.user.update({ where: { id }, data }),
deleteUser: (id: User["id"]) => prismaClient.user.delete({ where: { id } }),
async createVerificationToken(data: VerificationToken) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id: _, ...verificationToken } = await prismaClient.verificationToken.create({
data,
});
@ -52,6 +54,7 @@ export default function CalComAdapter(prismaClient: PrismaClient) {
},
async useVerificationToken(identifier_token: Prisma.VerificationTokenIdentifierTokenCompoundUniqueInput) {
try {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id: _, ...verificationToken } = await prismaClient.verificationToken.delete({
where: { identifier_token },
});
@ -59,8 +62,9 @@ export default function CalComAdapter(prismaClient: PrismaClient) {
} catch (error) {
// If token already used/deleted, just return null
// https://www.prisma.io/docs/reference/api-reference/error-reference#p2025
// @ts-ignore
if (error.code === "P2025") return null;
if (error instanceof PrismaClientKnownRequestError) {
if (error.code === "P2025") return null;
}
throw error;
}
},

View File

@ -1,3 +1,5 @@
import noop from "lodash/noop";
const data: Record<string, number> = {};
/**
* Starts an iteration from `0` to `length - 1` with batch size `batch`
@ -20,9 +22,9 @@ export const doWorkAsync = ({
}: {
name: string;
length: number;
callback: Function;
done?: Function;
batchDone?: Function;
callback: (i: number, b?: boolean) => void;
done?: () => void;
batchDone?: () => void;
batch: number;
offsetStart?: number;
__pending?: boolean;
@ -32,8 +34,8 @@ export const doWorkAsync = ({
const lastIndex = length - 1;
const offsetEndExclusive = offsetStart + stepLength;
batchDone = batchDone || (() => {});
done = done || (() => {});
batchDone = batchDone || noop;
done = done || noop;
if (!__pending && data[name]) {
cancelAnimationFrame(data[name]);

View File

@ -1,4 +1,3 @@
import { recurringEvent } from "@calcom/prisma/zod-utils";
import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
import AttendeeAwaitingPaymentEmail from "@lib/emails/templates/attendee-awaiting-payment-email";

View File

@ -4,7 +4,7 @@ import { getErrorFromUnknown } from "@calcom/lib/errors";
import { serverConfig } from "@calcom/lib/serverConfig";
export default class BaseEmail {
name: string = "";
name = "";
protected getNodeMailerPayload(): Record<string, unknown> {
return {};

View File

@ -50,6 +50,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
`.replace(/(<([^>]+)>)/gi, "");
}
@ -97,6 +98,7 @@ ${this.getAdditionalNotes()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -49,6 +49,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.cancellationReason && this.getCancellationReason()}
`.replace(/(<([^>]+)>)/gi, "");
}
@ -98,6 +99,7 @@ ${this.calEvent.cancellationReason && this.getCancellationReason()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.cancellationReason && this.getCancellationReason()}
</div>
</td>

View File

@ -51,6 +51,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.getRejectionReason()}
`.replace(/(<([^>]+)>)/gi, "");
}
@ -102,6 +103,7 @@ ${this.getRejectionReason()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.getRejectionReason()}
</div>
</td>

View File

@ -62,6 +62,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
`.replace(/(<([^>]+)>)/gi, "");
}
@ -119,6 +120,7 @@ ${this.getAdditionalNotes()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -105,6 +105,7 @@ ${this.calEvent.organizer.language.translate("request_reschedule_subtitle", {
${this.getWhat()}
${this.getWhen()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
${getCancelLink(this.calEvent)}
`.replace(/(<([^>]+)>)/gi, "");
@ -154,6 +155,7 @@ ${getCancelLink(this.calEvent)}
${this.getWhen()}
${this.getWho()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -57,6 +57,7 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.attendee.language.translate("need_to_reschedule_or_cancel")}
${getCancelLink(this.calEvent)}
`.replace(/(<([^>]+)>)/gi, "");
@ -69,6 +70,7 @@ ${this.getWhat()}
${this.getWhen()}
${this.getLocation()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
`.replace(/(<([^>]+)>)/gi, "");
}
@ -116,6 +118,7 @@ ${this.getAdditionalNotes()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -169,6 +169,7 @@ ${getRichDescription(this.calEvent)}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>
@ -316,6 +317,28 @@ ${getRichDescription(this.calEvent)}
`;
}
protected getCustomInputs(): string {
const { customInputs } = this.calEvent;
if (!customInputs) return "";
const customInputsString = Object.keys(customInputs)
.map((key) => {
if (customInputs[key] !== "") {
return `
<p style="height: 6px"></p>
<div style="line-height: 6px;">
<p style="color: #494949;">${key}</p>
<p style="color: #494949; font-weight: 400;">
${customInputs[key]}
</p>
</div>
`;
}
})
.join("");
return customInputsString;
}
protected getRejectionReason(): string {
if (!this.calEvent.rejectionReason) return "";
return `
@ -405,6 +428,12 @@ ${getRichDescription(this.calEvent)}
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
providerName || this.calEvent.location
}</p>
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
(providerName === "Zoom" || providerName === "Google") &&
`
${this.calEvent.organizer.language.translate("meeting_url_provided_after_confirmed")}
`
}</p>
</div>
`;
}

View File

@ -58,6 +58,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.cancellationReason && this.getCancellationReason()}
`.replace(/(<([^>]+)>)/gi, "");
}
@ -106,6 +107,7 @@ ${this.calEvent.cancellationReason && this.getCancellationReason()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.cancellationReason && this.getCancellationReason()}
</div>
</td>

View File

@ -60,6 +60,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
`.replace(/(<([^>]+)>)/gi, "");
}
@ -139,6 +140,7 @@ ${this.getAdditionalNotes()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -59,6 +59,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
`.replace(/(<([^>]+)>)/gi, "");
@ -110,6 +111,7 @@ ${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
${this.getWho()}
${this.getLocation()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -59,6 +59,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.organizer.language.translate("confirm_or_reject_request")}
${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
`.replace(/(<([^>]+)>)/gi, "");
@ -108,6 +109,7 @@ ${process.env.NEXT_PUBLIC_WEBAPP_URL} + "/bookings/upcoming"
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -115,6 +115,7 @@ ${this.getWhat()}
${this.getWhen()}
${this.getLocation()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
${getCancelLink(this.calEvent)}
`.replace(/(<([^>]+)>)/gi, "");
@ -166,6 +167,7 @@ ${getCancelLink(this.calEvent)}
${this.getWhen()}
${this.getWho()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -64,6 +64,7 @@ ${this.getWhen()}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")}
${getCancelLink(this.calEvent)}
`.replace(/(<([^>]+)>)/gi, "");
@ -113,6 +114,7 @@ ${getCancelLink(this.calEvent)}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>

View File

@ -162,6 +162,7 @@ ${getRichDescription(this.calEvent)}
${this.getLocation()}
${this.getDescription()}
${this.getAdditionalNotes()}
${this.getCustomInputs()}
</div>
</td>
</tr>
@ -303,6 +304,28 @@ ${getRichDescription(this.calEvent)}
`;
}
protected getCustomInputs(): string {
const { customInputs } = this.calEvent;
if (!customInputs) return "";
const customInputsString = Object.keys(customInputs)
.map((key) => {
if (customInputs[key] !== "") {
return `
<p style="height: 6px"></p>
<div style="line-height: 6px;">
<p style="color: #494949;">${key}</p>
<p style="color: #494949; font-weight: 400;">
${customInputs[key]}
</p>
</div>
`;
}
})
.join("");
return customInputsString;
}
protected getDescription(): string {
if (!this.calEvent.description) return "";
return `
@ -333,17 +356,17 @@ ${getRichDescription(this.calEvent)}
const meetingId = this.calEvent.videoCallData.id;
const meetingPassword = this.calEvent.videoCallData.password;
const meetingUrl = this.calEvent.videoCallData.url;
return `
<p style="height: 6px"></p>
<div style="line-height: 6px;">
<p style="color: #494949;">${this.calEvent.organizer.language.translate("where")}</p>
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName} ${
meetingUrl &&
`<a href="${meetingUrl}" target="_blank" alt="${this.calEvent.organizer.language.translate(
"meeting_url"
)}"><img src="${linkIcon()}" width="12px"></img></a>`
}</p>
<p style="color: #494949; font-weight: 400; line-height: 24px;">${providerName}
${
meetingUrl &&
`<a href="${meetingUrl}" target="_blank" alt="${this.calEvent.organizer.language.translate(
"meeting_url"
)}"><img src="${linkIcon()}" width="12px"/></a>`
}</p>
${
meetingId &&
`<div style="color: #494949; font-weight: 400; line-height: 24px;">${this.calEvent.organizer.language.translate(
@ -370,7 +393,6 @@ ${getRichDescription(this.calEvent)}
if (this.calEvent.additionInformation?.hangoutLink) {
const hangoutLink: string = this.calEvent.additionInformation.hangoutLink;
return `
<p style="height: 6px"></p>
<div style="line-height: 6px;">
@ -395,6 +417,12 @@ ${getRichDescription(this.calEvent)}
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
providerName || this.calEvent.location
}</p>
<p style="color: #494949; font-weight: 400; line-height: 24px;">${
(providerName === "Zoom" || providerName === "Google") &&
`
${this.calEvent.organizer.language.translate("meeting_url_provided_after_confirmed")}
`
}</p>
</div>
`;
}

View File

@ -8,6 +8,7 @@ async function getBooking(prisma: PrismaClient, uid: string) {
select: {
startTime: true,
description: true,
customInputs: true,
attendees: {
select: {
email: true,

View File

@ -1,5 +1,4 @@
import Head from "next/head";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useEmbedTheme } from "@calcom/embed-core";

View File

@ -0,0 +1,40 @@
import { EventType, PeriodType } from "@prisma/client";
import dayjs from "dayjs";
function isOutOfBounds(
time: dayjs.ConfigType,
{
periodType,
periodDays,
periodCountCalendarDays,
periodStartDate,
periodEndDate,
}: Pick<
EventType,
"periodType" | "periodDays" | "periodCountCalendarDays" | "periodStartDate" | "periodEndDate"
>
) {
const date = dayjs(time);
periodDays = periodDays || 0;
switch (periodType) {
case PeriodType.ROLLING: {
const periodRollingEndDay = periodCountCalendarDays
? dayjs().utcOffset(date.utcOffset()).add(periodDays, "days").endOf("day")
: dayjs().utcOffset(date.utcOffset()).businessDaysAdd(periodDays).endOf("day");
return date.endOf("day").isAfter(periodRollingEndDay);
}
case PeriodType.RANGE: {
const periodRangeStartDay = dayjs(periodStartDate).utcOffset(date.utcOffset()).endOf("day");
const periodRangeEndDay = dayjs(periodEndDate).utcOffset(date.utcOffset()).endOf("day");
return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay);
}
case PeriodType.UNLIMITED:
default:
return false;
}
}
export default isOutOfBounds;

View File

@ -1,4 +1,4 @@
import { Team, User } from ".prisma/client";
import { Team, User } from "@prisma/client";
export function isSuccessRedirectAvailable(
eventType: {

View File

@ -2,7 +2,6 @@ import dayjs, { Dayjs } from "dayjs";
import { I18n } from "next-i18next";
import { RRule } from "rrule";
import { recurringEvent } from "@calcom/prisma/zod-utils";
import { RecurringEvent } from "@calcom/types/Calendar";
import { detectBrowserTimeFormat } from "@lib/timeFormat";
@ -29,6 +28,7 @@ export const parseRecurringDates = (
}: { startDate: string | null | Dayjs; recurringEvent: RecurringEvent; recurringCount: number },
i18n: I18n
): [string[], Date[]] => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { count, ...restRecurringEvent } = recurringEvent;
const rule = new RRule({
...restRecurringEvent,

View File

@ -89,7 +89,7 @@ const getSlots = ({ inviteeDate, frequency, minimumBookingNotice, workingHours,
computedLocalWorkingHours.push(tempComputeTimeFrame);
}
});
computedLocalWorkingHours.forEach((item, index) => {
computedLocalWorkingHours.forEach((item) => {
slotsTimeFrameAvailable.push(...splitAvailableTime(item.startTime, item.endTime, frequency, eventLength));
});

View File

@ -1,6 +1,11 @@
import { jitsuClient, JitsuClient } from "@jitsu/sdk-js";
import React, { useContext } from "react";
declare global {
// eslint-disable-next-line no-var
var jitsu: JitsuClient | undefined;
}
/**
* Enumeration of all event types that are being sent
* to telemetry collection.
@ -11,6 +16,7 @@ export const telemetryEventTypes = {
bookingCancelled: "booking_cancelled",
importSubmitted: "import_submitted",
googleLogin: "google_login",
login: "login",
samlLogin: "saml_login",
samlConfig: "saml_config",
embedView: "embed_view",
@ -48,7 +54,10 @@ function isLocalhost(host: string) {
* Collects page parameters and makes sure no sensitive data made it to telemetry
* @param route current next.js route
*/
export function collectPageParameters(route?: string, extraData: Record<string, any> = {}): any {
export function collectPageParameters(
route?: string,
extraData: Record<string, unknown> = {}
): Record<string, unknown> {
const host = document.location.hostname;
const maskedHost = isLocalhost(host) ? "localhost" : "masked";
//starts with ''
@ -77,13 +86,7 @@ function createTelemetryClient(): TelemetryClient {
if (!window) {
console.warn("Jitsu has been called during SSR, this scenario isn't supported yet");
return;
} else if (
// FIXME
// @ts-ignore
!window["jitsu"]
) {
// FIXME
// @ts-ignore
} else if (!window["jitsu"]) {
window["jitsu"] = jitsuClient({
log_level: "ERROR",
tracking_host: "https://t.calendso.com",
@ -92,8 +95,6 @@ function createTelemetryClient(): TelemetryClient {
capture_3rd_party_cookies: false,
});
}
// FIXME
// @ts-ignore
const res = callback(window["jitsu"]);
if (res && typeof res["catch"] === "function") {
res.catch((e) => {

View File

@ -24,7 +24,7 @@ export type BookingCreateBody = {
timeZone: string;
user?: string | string[];
language: string;
customInputs: { label: string; value: string }[];
customInputs: { label: string; value: string | boolean }[];
metadata: {
[key: string]: string;
};

View File

@ -30,21 +30,21 @@
"@calcom/app-store": "*",
"@calcom/core": "*",
"@calcom/ee": "*",
"@calcom/embed-core": "*",
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/stripe": "*",
"@calcom/tsconfig": "*",
"@calcom/ui": "*",
"@calcom/embed-core": "*",
"@daily-co/daily-js": "^0.21.0",
"@daily-co/daily-js": "^0.26.0",
"@glidejs/glide": "^3.5.2",
"@heroicons/react": "^1.0.6",
"@hookform/error-message": "^2.0.0",
"@hookform/resolvers": "^2.8.5",
"@hookform/resolvers": "^2.8.9",
"@jitsu/sdk-js": "^2.2.4",
"@metamask/providers": "^8.1.1",
"@next-auth/prisma-adapter": "^1.0.3",
"@next/bundle-analyzer": "12.1.0",
"@next/bundle-analyzer": "12.1.6",
"@radix-ui/react-avatar": "^0.1.0",
"@radix-ui/react-collapsible": "^0.1.0",
"@radix-ui/react-dialog": "^0.1.0",
@ -54,20 +54,20 @@
"@radix-ui/react-slider": "^0.1.1",
"@radix-ui/react-switch": "^0.1.1",
"@radix-ui/react-tooltip": "^0.1.0",
"@stripe/react-stripe-js": "^1.4.1",
"@stripe/stripe-js": "^1.16.0",
"@trpc/client": "^9.22.0",
"@trpc/next": "^9.22.0",
"@trpc/react": "^9.22.0",
"@trpc/server": "^9.22.0",
"@stripe/react-stripe-js": "^1.8.0",
"@stripe/stripe-js": "^1.29.0",
"@trpc/client": "^9.23.4",
"@trpc/next": "^9.23.4",
"@trpc/react": "^9.23.4",
"@trpc/server": "^9.23.4",
"@vercel/edge-functions-ui": "^0.2.1",
"@wojtekmaj/react-daterange-picker": "^3.3.1",
"accept-language-parser": "^1.5.0",
"async": "^3.2.1",
"bcryptjs": "^2.4.3",
"classnames": "^2.3.1",
"dayjs": "^1.10.4",
"dayjs-business-time": "^1.0.4",
"dayjs": "^1.11.2",
"dayjs-business-days2": "^1.1.0",
"googleapis": "^84.0.0",
"gray-matter": "^4.0.3",
"handlebars": "^4.7.7",
@ -78,78 +78,77 @@
"lodash": "^4.17.21",
"micro": "^9.3.4",
"mime-types": "^2.1.35",
"next": "^12.1.0",
"next-auth": "^4.3.3",
"next-i18next": "^8.9.0",
"next-mdx-remote": "^4.0.2",
"next": "^12.1.6",
"next-auth": "^4.3.4",
"next-i18next": "^11.0.0",
"next-mdx-remote": "^4.0.3",
"next-seo": "^4.26.0",
"next-transpile-modules": "^9.0.0",
"nodemailer": "^6.7.2",
"nodemailer": "^6.7.5",
"otplib": "^12.0.1",
"qrcode": "^1.5.0",
"react": "^17.0.2",
"react": "^18.1.0",
"react-colorful": "^5.5.1",
"react-date-picker": "^8.3.6",
"react-digit-input": "^2.1.0",
"react-dom": "^17.0.2",
"react-dom": "^18.1.0",
"react-easy-crop": "^3.5.2",
"react-hook-form": "^7.29.0",
"react-hook-form": "^7.31.1",
"react-hot-toast": "^2.1.0",
"react-intl": "^5.22.0",
"react-intl": "^5.25.1",
"react-live-chat-loader": "^2.7.3",
"react-multi-email": "^0.5.3",
"react-phone-number-input": "^3.1.41",
"react-query": "^3.33.7",
"react-router-dom": "^5.2.0",
"react-select": "^5.2.1",
"react-phone-number-input": "^3.1.52",
"react-query": "^3.39.0",
"react-select": "^5.3.2",
"react-timezone-select": "^1.3.1",
"react-use-intercom": "1.4.0",
"react-use-intercom": "1.5.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6",
"react-window": "^1.8.7",
"rrule": "^2.6.9",
"short-uuid": "^4.2.0",
"stripe": "^8.191.0",
"superjson": "1.8.1",
"stripe": "^9.1.0",
"superjson": "1.9.1",
"uuid": "^8.3.2",
"web3": "^1.6.1",
"zod": "^3.14.4"
"web3": "^1.7.3",
"zod": "^3.16.0"
},
"devDependencies": {
"@babel/core": "^7.17.8",
"@babel/core": "^7.17.10",
"@calcom/config": "*",
"@calcom/types": "*",
"@microsoft/microsoft-graph-types-beta": "0.15.0-preview",
"@playwright/test": "^1.18.1",
"@playwright/test": "^1.22.1",
"@types/accept-language-parser": "1.5.2",
"@types/async": "^3.2.10",
"@types/async": "^3.2.13",
"@types/bcryptjs": "^2.4.2",
"@types/glidejs__glide": "^3.4.1",
"@types/jest": "^27.0.3",
"@types/lodash": "^4.14.177",
"@types/micro": "^7.3.6",
"@types/glidejs__glide": "^3.4.2",
"@types/jest": "^27.5.1",
"@types/lodash": "^4.14.182",
"@types/micro": "7.3.6",
"@types/mime-types": "^2.1.1",
"@types/module-alias": "^2.0.1",
"@types/node": "^16.11.24",
"@types/node": "14.17.6",
"@types/nodemailer": "^6.4.4",
"@types/qrcode": "^1.4.1",
"@types/react": "^17.0.37",
"@types/react": "18.0.9",
"@types/react-phone-number-input": "^3.0.13",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/stripe": "^8.0.417",
"@types/uuid": "8.3.1",
"autoprefixer": "^10.4.0",
"autoprefixer": "^10.4.7",
"babel-jest": "^27.3.1",
"env-cmd": "10.1.0",
"eslint": "^8.10.0",
"eslint": "^8.15.0",
"jest": "^26.0.0",
"mockdate": "^3.0.5",
"module-alias": "^2.2.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.4",
"tailwindcss": "^3.0.23",
"postcss": "^8.4.13",
"tailwindcss": "^3.0.24",
"ts-jest": "^26.0.0",
"ts-node": "^10.6.0",
"typescript": "^4.5.3"
"typescript": "^4.6.4"
}
}

View File

@ -15,7 +15,7 @@ export default function Custom404() {
const { t } = useLocale();
const router = useRouter();
const username = router.asPath.replace("%20", "-");
const username = router.asPath.replace("%20", "-").split(/[?#]/)[0];
const links = [
{

View File

@ -6,7 +6,7 @@ import { GetServerSidePropsContext } from "next";
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { Toaster } from "react-hot-toast";
import { JSONObject } from "superjson/dist/types";
@ -18,7 +18,6 @@ import defaultEvents, {
getUsernameSlugLink,
} from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RecurringEvent } from "@calcom/types/Calendar";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import useTheme from "@lib/hooks/useTheme";
@ -71,7 +70,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="p-8 text-center text-gray-400 dark:text-white">
<h2 className="font-cal mb-2 text-3xl text-gray-600 dark:text-white">{" " + t("unavailable")}</h2>
<p className="mx-auto max-w-md">{t("user_dynamic_booking_disabled")}</p>
<p className="mx-auto max-w-md">{t("user_dynamic_booking_disabled") as string}</p>
</div>
</div>
</div>
@ -80,7 +79,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
{eventTypes.map((type, index) => (
<li
key={index}
className="hover:border-brand group relative rounded-sm border border-neutral-200 bg-white hover:bg-gray-50 dark:border-0 dark:bg-neutral-900 dark:hover:border-neutral-600">
className="hover:border-brand group relative rounded-sm border border-neutral-200 bg-white hover:bg-gray-50 dark:border-neutral-700 dark:bg-gray-800 dark:hover:border-neutral-600">
<ArrowRightIcon className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
<Link href={getUsernameSlugLink({ users: props.users, slug: type.slug })}>
<a className="flex justify-between px-6 py-4" data-testid="event-type-link">
@ -167,7 +166,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
<h2 className="font-cal mb-2 text-3xl text-gray-600 dark:text-white">
😴{" " + t("user_away")}
</h2>
<p className="mx-auto max-w-md">{t("user_away_description")}</p>
<p className="mx-auto max-w-md">{t("user_away_description") as string}</p>
</div>
</div>
) : isDynamicGroup ? ( //When we deal with dynamic group (users > 1)
@ -225,8 +224,10 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
{eventTypes.length === 0 && (
<div className="overflow-hidden rounded-sm border dark:border-gray-900">
<div className="p-8 text-center text-gray-400 dark:text-white">
<h2 className="font-cal mb-2 text-3xl text-gray-600 dark:text-white">{t("uh_oh")}</h2>
<p className="mx-auto max-w-md">{t("no_event_types_have_been_setup")}</p>
<h2 className="font-cal mb-2 text-3xl text-gray-600 dark:text-white">
{t("uh_oh") as string}
</h2>
<p className="mx-auto max-w-md">{t("no_event_types_have_been_setup") as string}</p>
</div>
</div>
)}
@ -287,7 +288,7 @@ const getEventTypesWithHiddenFromDB = async (userId: number, plan: UserPlan) =>
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
const crypto = require("crypto");
const crypto = await import("crypto");
const usernameList = getUsernameList(context.query.user as string);
const dataFetchStart = Date.now();

View File

@ -1,7 +1,5 @@
import { DefaultSeo } from "next-seo";
import Head from "next/head";
import { useEffect } from "react";
// import { ReactQueryDevtools } from "react-query/devtools";
import superjson from "superjson";
import "@calcom/embed-core/src/embed-iframe";

View File

@ -34,7 +34,7 @@ const CustomError: NextPage<CustomErrorProps> = (props) => {
// getInitialProps is not called in case of
// https://github.com/vercel/next.js/issues/8592. As a workaround, we pass
// err via _app.tsx so it can be captured
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const e = getErrorFromUnknown(err);
// can be captured here
// e.g. Sentry.captureException(e);

View File

@ -1,4 +1,3 @@
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { IdentityProvider, UserPermissionRole } from "@prisma/client";
import { readFileSync } from "fs";
import Handlebars from "handlebars";
@ -185,6 +184,7 @@ if (true) {
}
const calcomAdapter = CalComAdapter(prisma);
export default NextAuth({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
adapter: calcomAdapter,
session: {
@ -203,6 +203,7 @@ export default NextAuth({
const autoMergeIdentities = async () => {
if (!hostedCal) {
const existingUser = await prisma.user.findFirst({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
where: { email: token.email! },
});

View File

@ -1,6 +1,8 @@
import { OAuthReqBody } from "@boxyhq/saml-jackson";
import { NextApiRequest, NextApiResponse } from "next";
import { HttpError } from "@calcom/lib/http-error";
import jackson from "@lib/jackson";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@ -12,10 +14,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { oauthController } = await jackson();
const { redirect_url } = await oauthController.authorize(req.query as unknown as OAuthReqBody);
res.redirect(302, redirect_url);
} catch (err: any) {
console.error("authorize error:", err);
const { message, statusCode = 500 } = err;
res.status(statusCode).send(message);
} catch (err: unknown) {
if (err instanceof HttpError) {
console.error("authorize error:", err);
const { message, statusCode = 500 } = err;
return res.status(statusCode).send(message);
}
return res.status(500).send("Unknown error");
}
}

View File

@ -1,5 +1,7 @@
import { NextApiRequest, NextApiResponse } from "next";
import { HttpError } from "@calcom/lib/http-error";
import jackson from "@lib/jackson";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@ -12,10 +14,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { redirect_url } = await oauthController.samlResponse(req.body);
res.redirect(302, redirect_url);
} catch (err: any) {
console.error("callback error:", err);
const { message, statusCode = 500 } = err;
res.status(statusCode).send(message);
} catch (err: unknown) {
if (err instanceof HttpError) {
console.error("callback error:", err);
const { message, statusCode = 500 } = err;
return res.status(statusCode).send(message);
}
return res.status(500).send("Unknown error");
}
}

View File

@ -1,17 +1,15 @@
import { Prisma, User, Booking, SchedulingType, BookingStatus } from "@prisma/client";
import { Booking, BookingStatus, Prisma, SchedulingType, User } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import rrule from "rrule";
import EventManager from "@calcom/core/EventManager";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import logger from "@calcom/lib/logger";
import type { AdditionInformation, RecurringEvent } from "@calcom/types/Calendar";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { AdditionInformation, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
import { refund } from "@ee/lib/stripe/server";
import { asStringOrNull } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import { sendDeclinedEmails } from "@lib/emails/email-manager";
import { sendScheduledEmails } from "@lib/emails/email-manager";
import { sendDeclinedEmails, sendScheduledEmails } from "@lib/emails/email-manager";
import prisma from "@lib/prisma";
import { BookingConfirmBody } from "@lib/types/booking";
@ -90,6 +88,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
select: {
title: true,
description: true,
customInputs: true,
startTime: true,
endTime: true,
confirmed: true,
@ -157,6 +156,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
type: booking.title,
title: booking.title,
description: booking.description,
customInputs: isPrismaObjOrUndefined(booking.customInputs),
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {

View File

@ -1,7 +1,7 @@
import { BookingStatus, Credential, Prisma, SchedulingType, WebhookTriggerEvents } from "@prisma/client";
import async from "async";
import dayjs from "dayjs";
import dayjsBusinessTime from "dayjs-business-time";
import dayjsBusinessTime from "dayjs-business-days2";
import isBetween from "dayjs/plugin/isBetween";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
@ -11,6 +11,7 @@ import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import EventManager from "@calcom/core/EventManager";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import logger from "@calcom/lib/logger";
@ -28,6 +29,7 @@ import {
import { ensureArray } from "@lib/ensureArray";
import { getEventName } from "@lib/event";
import getBusyTimes from "@lib/getBusyTimes";
import isOutOfBounds from "@lib/isOutOfBounds";
import prisma from "@lib/prisma";
import { BookingCreateBody } from "@lib/types/booking";
import sendPayload from "@lib/webhooks/sendPayload";
@ -104,32 +106,6 @@ function isAvailable(busyTimes: BufferedBusyTimes, time: dayjs.ConfigType, lengt
return t;
}
function isOutOfBounds(
time: dayjs.ConfigType,
{ periodType, periodDays, periodCountCalendarDays, periodStartDate, periodEndDate, timeZone }: any // FIXME types
): boolean {
const date = dayjs(time);
switch (periodType) {
case "rolling": {
const periodRollingEndDay = periodCountCalendarDays
? dayjs().tz(timeZone).add(periodDays, "days").endOf("day")
: dayjs().tz(timeZone).addBusinessTime(periodDays, "days").endOf("day");
return date.endOf("day").isAfter(periodRollingEndDay);
}
case "range": {
const periodRangeStartDay = dayjs(periodStartDate).tz(timeZone).endOf("day");
const periodRangeEndDay = dayjs(periodEndDate).tz(timeZone).endOf("day");
return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay);
}
case "unlimited":
default:
return false;
}
}
const userSelect = Prisma.validator<Prisma.UserArgs>()({
select: {
id: true,
@ -348,18 +324,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
t: tOrganizer,
};
const additionalNotes =
reqBody.notes +
reqBody.customInputs.reduce(
(str, input) => str + "<br /><br />" + input.label + ":<br />" + input.value,
""
);
const additionalNotes = reqBody.notes;
const customInputs = {} as NonNullable<CalendarEvent["customInputs"]>;
if (reqBody.customInputs.length > 0) {
reqBody.customInputs.forEach(({ label, value }) => {
customInputs[label] = value;
});
}
const evt: CalendarEvent = {
type: eventType.title,
title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately
description: eventType.description,
additionalNotes,
customInputs,
startTime: reqBody.start,
endTime: reqBody.end,
organizer: {
@ -456,6 +436,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
startTime: dayjs(evt.startTime).toDate(),
endTime: dayjs(evt.endTime).toDate(),
description: evt.additionalNotes,
customInputs: isPrismaObjOrUndefined(evt.customInputs),
confirmed: (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid,
location: evt.location,
eventType: eventTypeRel,
@ -613,7 +594,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
periodEndDate: eventType.periodEndDate,
periodStartDate: eventType.periodStartDate,
periodCountCalendarDays: eventType.periodCountCalendarDays,
timeZone: currentUser.timeZone,
});
} catch {
log.debug({
@ -731,6 +711,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...evt,
additionInformation: metadata,
additionalNotes,
customInputs,
},
reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
);

View File

@ -91,6 +91,8 @@ const handler = async (
},
});
if (bookingToReschedule.userId !== userOwner.id) throw new Error("UNAUTHORIZED");
if (bookingToReschedule && userOwner) {
let event: Partial<EventType> = {};
if (bookingToReschedule.eventTypeId) {

View File

@ -6,13 +6,14 @@ import { NextApiRequest, NextApiResponse } from "next";
import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
import { getCalendar } from "@calcom/core/CalendarManager";
import { deleteMeeting } from "@calcom/core/videoClient";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { refund } from "@ee/lib/stripe/server";
import { asStringOrNull } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import { sendCancelledEmails } from "@lib/emails/email-manager";
import prisma from "@lib/prisma";
import sendPayload from "@lib/webhooks/sendPayload";
import getWebhooks from "@lib/webhooks/subscriptions";
@ -33,7 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
uid,
},
select: {
id: true,
...bookingMinimalSelect,
userId: true,
user: {
select: {
@ -45,7 +46,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
destinationCalendar: true,
},
},
attendees: true,
location: true,
references: {
select: {
@ -56,15 +56,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
payment: true,
paid: true,
title: true,
eventType: {
select: {
title: true,
},
},
description: true,
startTime: true,
endTime: true,
uid: true,
eventTypeId: true,
destinationCalendar: true,
@ -115,6 +111,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
title: bookingToDelete?.title,
type: (bookingToDelete?.eventType?.title as string) || bookingToDelete?.title,
description: bookingToDelete?.description || "",
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
startTime: bookingToDelete?.startTime ? dayjs(bookingToDelete.startTime).format() : "",
endTime: bookingToDelete?.endTime ? dayjs(bookingToDelete.endTime).format() : "",
organizer: {
@ -183,6 +180,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
type: bookingToDelete?.eventType?.title as string,
title: bookingToDelete.title,
description: bookingToDelete.description ?? "",
customInputs: isPrismaObjOrUndefined(bookingToDelete.customInputs),
startTime: bookingToDelete.startTime.toISOString(),
endTime: bookingToDelete.endTime.toISOString(),
organizer: {

View File

@ -2,10 +2,11 @@ import { ReminderType } from "@prisma/client";
import dayjs from "dayjs";
import type { NextApiRequest, NextApiResponse } from "next";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { sendOrganizerRequestReminderEmail } from "@lib/emails/email-manager";
import prisma from "@lib/prisma";
import { getTranslation } from "@server/lib/i18n";
@ -32,12 +33,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
},
select: {
title: true,
description: true,
...bookingMinimalSelect,
location: true,
startTime: true,
endTime: true,
attendees: true,
user: {
select: {
email: true,
@ -48,7 +45,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
destinationCalendar: true,
},
},
id: true,
uid: true,
destinationCalendar: true,
},
@ -94,6 +90,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
type: booking.title,
title: booking.title,
description: booking.description || undefined,
customInputs: isPrismaObjOrUndefined(booking.customInputs),
location: booking.location ?? "",
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),

View File

@ -25,7 +25,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (typeof handler !== "function")
throw new HttpError({ statusCode: 404, message: `API handler not found` });
const response = await handler(req, res);
await handler(req, res);
return res.status(200);
} catch (error) {

View File

@ -1,4 +1,3 @@
import { Prisma } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { getStripeCustomerId } from "@calcom/stripe/customer";

View File

@ -63,16 +63,13 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
</h2>
</div>
<p>{t("password_has_been_reset_login")}</p>
{
// eslint-disable-next-line @next/next/link-passhref
<Link href="/auth/login">
<button
type="button"
className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
{t("login")}
</button>
</Link>
}
<Link href="/auth/login">
<button
type="button"
className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
{t("login")}
</button>
</Link>
</div>
</>
);

View File

@ -45,6 +45,8 @@ export default function Login({
const { t } = useLocale();
const router = useRouter();
const form = useForm<LoginValues>();
const { formState } = form;
const { isSubmitting } = formState;
const [twoFactorRequired, setTwoFactorRequired] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
@ -97,25 +99,27 @@ export default function Login({
<AuthContainer
title={t("login")}
description={t("login")}
loading={form.formState.isSubmitting}
showLogo
heading={twoFactorRequired ? t("2fa_code") : t("sign_in_account")}
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
<Form
form={form}
className="space-y-6"
handleSubmit={(values) => {
signIn<"credentials">("credentials", { ...values, callbackUrl, redirect: false })
.then((res) => {
if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
// we're logged in! let's do a hard refresh to the desired url
else if (!res.error) router.push(callbackUrl);
// reveal two factor input if required
else if (res.error === ErrorCode.SecondFactorRequired) setTwoFactorRequired(true);
// fallback if error not found
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
})
.catch(() => setErrorMessage(errorMessages[ErrorCode.InternalServerError]));
handleSubmit={async (values) => {
setErrorMessage(null);
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.login, collectPageParameters()));
const res = await signIn<"credentials">("credentials", {
...values,
callbackUrl,
redirect: false,
});
if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
// we're logged in! let's do a hard refresh to the desired url
else if (!res.error) router.push(callbackUrl);
// reveal two factor input if required
else if (res.error === ErrorCode.SecondFactorRequired) setTwoFactorRequired(true);
// fallback if error not found
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
}}
data-testid="login-form">
<div>
@ -156,10 +160,7 @@ export default function Login({
{errorMessage && <Alert severity="error" title={errorMessage} />}
<div className="flex space-y-2">
<Button
className="flex w-full justify-center"
type="submit"
disabled={form.formState.isSubmitting || (form.formState.isSubmitted && !twoFactorRequired)}>
<Button className="flex w-full justify-center" type="submit" disabled={isSubmitting}>
{twoFactorRequired ? t("submit") : t("sign_in")}
</Button>
</div>

View File

@ -2,8 +2,9 @@ import dayjs, { Dayjs } from "dayjs";
import utc from "dayjs/plugin/utc";
import { useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { QueryCell } from "@lib/QueryCell";
import { useLocale } from "@lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@lib/trpc";
import Loader from "@components/Loader";
@ -20,10 +21,10 @@ const AvailabilityView = ({ user }: { user: User }) => {
const [selectedDate, setSelectedDate] = useState(dayjs());
function convertMinsToHrsMins(mins: number) {
let h = Math.floor(mins / 60);
let m = mins % 60;
let hs = h < 10 ? "0" + h : h;
let ms = m < 10 ? "0" + m : m;
const h = Math.floor(mins / 60);
const m = mins % 60;
const hs = h < 10 ? "0" + h : h;
const ms = m < 10 ? "0" + m : m;
return `${hs}:${ms}`;
}

View File

@ -4,13 +4,13 @@ import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { Button } from "@calcom/ui/Button";
import { TextField } from "@calcom/ui/form/fields";
import { asStringOrUndefined } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { detectBrowserTimeFormat } from "@lib/timeFormat";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -168,12 +168,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
uid: asStringOrUndefined(context.query.uid),
},
select: {
id: true,
title: true,
description: true,
startTime: true,
endTime: true,
attendees: true,
...bookingMinimalSelect,
user: {
select: {
id: true,

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