Compare commits

...
427 Commits
Author SHA1 Message Date
zomars 6b0cffa79a v1.6.2 2022-05-30 12:53:07 -06:00
zomars 843daad481 Merge branch 'production'
# Conflicts:
#	.vscode/tasks.json
#	apps/console
#	apps/web/components/NavTabs.tsx
#	apps/web/components/SettingsShell.tsx
#	apps/web/components/booking/BookingListItem.tsx
#	apps/web/components/ui/TableActions.tsx
#	apps/web/components/ui/form/CheckboxField.tsx
#	apps/web/lib/emails/templates/attendee-scheduled-email.ts
#	apps/web/lib/emails/templates/organizer-scheduled-email.ts
#	apps/web/lib/types/booking.ts
#	apps/web/package.json
#	apps/web/pages/event-types/[type].tsx
#	apps/web/pages/getting-started.tsx
#	apps/web/pages/success.tsx
#	apps/web/public/static/locales/en/common.json
#	packages/config/package.json
#	packages/embeds/embed-core/package.json
#	packages/embeds/embed-react/package.json
#	packages/embeds/embed-react/src/index.ts
#	packages/embeds/embed-snippet/package.json
#	packages/embeds/embed-snippet/src/index.ts
#	packages/lib/isPrismaObj.ts
#	packages/lib/package.json
#	packages/ui/Dialog.tsx
#	yarn.lock
2022-05-30 12:50:31 -06:00
375517486c Fix booking detail page for cancelled events (#2905)
* fix booking detail page for cancelled events

* Fix typo

* fix bug when now attendees exist in booking

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
2022-05-30 20:17:50 +02:00
bc74014040 remove subtitle (#2925)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-30 17:26:58 +00:00
Hariom Balharaandkodiakhq[bot] 3035412cdc Add embed tests in CI (#2887)
* Add embed tests in CI

* Update e2e-embed.yml

* Run quick tests only

* Ignore certain folders

* Add embed-react tests as well

* Fix commands

* Dont run unit tests

* Update playwright.config.ts

* Update playwright.config.ts

* Update playwright.config.ts

* Create correct artifcats

* Create correct artifcats

* Fi ignore path

* Update package.json

* Add back embed-react typecheck

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-30 22:41:05 +05:30
f7cec8d71f fix(app-store): Giphy Ux fixes (#2746)
* fix(app-store): Giphy Ux fixes

* Add search placeholder

* Min height image on event type page

* Use regex in zod validation and fix design issues for figma row 1

* Add paddings as per discussion with Ciarán

* Fix getGiphyApiKey nomenclature

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

* Update regex to be more readable and allow enter to search

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-05-30 10:23:23 -06:00
Hariom Balharaandkodiakhq[bot] b22c562719 Allow less than 6 bookings and add proper min validation of 2+ (#2921)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-30 09:46:05 -06:00
e493b16466 Payment amount input leading 0 (#2836)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
2022-05-30 09:28:32 -06:00
Alex van Andel d1f117f17a Fixed typo in CheckboxField and wrapped description in <label> (#2924)
* Fixed typo in CheckboxField and wrapped description in <label>

* Make functionality identical to before

* Fixed use of infomationIconText

* Fix lint error (needs refactor, out of scope)
2022-05-30 14:51:18 +00:00
Peer Richelsen af5bc9d530 Update CONTRIBUTING.md 2022-05-30 12:44:28 +02:00
Peer Richelsen d94267cdc1 Update CONTRIBUTING.md 2022-05-30 12:37:57 +02:00
Alex van Andel a15fe78ddb Cleanup & removed usages of sparingly used Text/ files (#2904) 2022-05-30 11:36:23 +02:00
Hariom Balharaandkodiakhq[bot] e270be1b87 Fix event-type preview links on Vercel Previews (#2919)
* Fix website url

* Fix avatar paths everywhere

* Fix linting errors

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-30 07:57:48 +00:00
Hashenandkodiakhq[bot] 8fe969d3b8 remove unsed imports (#2895)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-30 05:19:22 +00:00
Omar López 9fced41cd0 Update CONTRIBUTING.md 2022-05-28 16:56:03 -06:00
Omar López b3e9a38231 Update CONTRIBUTING.md 2022-05-28 16:54:01 -06:00
Omar López c144c365de Update CONTRIBUTING.md 2022-05-28 16:46:24 -06:00
Omar López 00e728e968 Update CONTRIBUTING.md 2022-05-28 12:50:52 -06:00
Omar López be92cc3563 Update CONTRIBUTING.md 2022-05-28 11:37:58 -06:00
Omar López 99c6b92bfe Update CONTRIBUTING.md 2022-05-28 10:35:07 -06:00
Hashen ce0fb6c09e Update README.md (#2912) 2022-05-28 18:20:28 +02:00
Peer Richelsen dfd6bc1523 Update CONTRIBUTING.md 2022-05-28 18:10:56 +02:00
Peer Richelsen a542930b87 areas of expertise test 2022-05-28 18:07:10 +02:00
Peer Richelsen 46752bd0d8 Update README.md 2022-05-28 11:26:38 +02:00
Peer Richelsen 0458203281 added checkly and twitch badges 2022-05-28 11:26:11 +02:00
7b01bae829 Change location of booking (#2658)
* add functionality to change location in booking and send out mail

* add i18n

* change location with dropdown like in event-types

* small fixes and code clean up

* clean code

* improve format of current Location string

* clean code

* clear selection when dialog closed

* added mutation and changed props (first working verison)

* clean code

* clean code

* clean code

* clean code

* fix typo

* change maxHeight of select

* use useWatch for selectedLocation

* pass default values with props

* set current location directly in useState

* clear selected values when updating location

* fix trpc query for credentialst

* change icons for editing booking

* improve naming of variables

* remove unnecessary orderBy

* use locationOptionsToString method

* fix current location naming for Cal Video

* add phone input

* save phone number as location of booking

* remove input field for phone number for event-types

* fix redirection issue

* show previous selected location in event-type

* remove attendee number from selection for booking

* make first letter of location lowercase

* remove input field for attendee phone number

* clear Errors when changing location type

* set location details to optional

* clean code

* fixing issue that dropdown doesn't close when dialog opens

* clean code

* make overflow visibile in dialog

* fix existing bug with address not showing in event-type settings

* fix issue with losing focus after validation

* close rejection dialog

* small spelling fixes

* fix issue with LocationChangeEmail

* fix failing E2E test

* fix failing E2E test

* fix E2E test

* bug fix for saving user phone, and other minor changes

* merge main

* improve text

* fix UI of booking list

* Delete admin

* remove selection after update and submit

* add translation for error message

* add default values for checkbox

* add "your phone number" to locations on booking page

* remove duplicate attributes from viewer.bookings

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

* check if user is authorized to make changes to booking

* remove location string

* clan code for displayLocaitonPublicly checkbox

* fetch locationOptions on server side

* remove trpc query for credentials

* fix phone number input

* fix labels of host and attendee phone number for booking page

* Migrates edit location to tRPC

* Link elemnt should only be used in `a` tags

* Adds missin router

* Migrates locationOptions to tRPC query

* Type fixes

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-27 23:27:41 +00:00
zomars b869e70a7f Update turbo.json 2022-05-27 13:49:13 -06:00
Omar Lópezandkodiakhq[bot] 411497575a Ensures json fields on each call (#2893)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-27 11:14:08 -06:00
Hariom Balhara e1bf425c1c Fix failing builds (#2908) 2022-05-27 11:07:02 -06:00
Hariom Balhara 445cdd7cbf Fix @calcom/embed-react installation with TS project (#2870) 2022-05-27 09:37:02 -06:00
d6678f9b3a fix: overflowing text on teams page - using tailwind line-clamp (#2843)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Júlio Piubello da Silva Cabral <julio.piubello@gitstart.dev>
2022-05-27 12:38:50 +02:00
zomars 01025fa34c Submodule sync 2022-05-26 13:31:55 -06:00
zomars b973f971a7 Reverts license check on payment
Since it's a public page
2022-05-26 12:05:51 -06:00
zomars 2f832800fc Update turbo.json 2022-05-26 11:44:23 -06:00
Omar Lópezandkodiakhq[bot] 9df4867fca License server (#2379)
* WIP License server

* WIP

* Moves locations to App Store and Core

* LocationType fixes

* Runs db migrations post-deploy

* WIP

* WIP

* Cleanup

* WIP

* WIP

* Decouples translations from NavTabs

* Adds admin submodule

* Adds admin submodule

* Sync dependencies

* WIP

* WIP

* Updates submodules

* Renames package

* Updates submodules

* Adds scripts for console

* Updates license checker URL

* Updates admin

* Adds staging/prod admin console links

* Update yarn.lock

* Update NavTabs.tsx

* WIP

* Update admin

* WIP

* Adds hint to InputField

* Update admin

* Adds turbo admin dependecies

* Update admin

* Prevents redirection on form submit

* Form warning fixes

* Update admin

* Form fixes

* Update yarn.lock

* Update admin

* Update admin

* Update admin

* Adds withLicenseRequired HOC

* Adds LicenseRequired to EE components

* Admin deploy fix?

* Updates submodules

* Use relative inside lib

* type fixes

* Fixes turbo race condition

* Relocates admin to console

* Relocates admin to console

* Update console

* Update api

* Update turbo.json

* Update ErrorBoundary.tsx

* Update defaultEvents.ts

* Update checkLicense.ts

* Update yarn.lock

* Skip on E2E

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-26 11:07:14 -06:00
Hashen 6940c8d6cc Redundant conditional expression (#2894)
* remove redundant conditional expressions

* remove redundant conditional expressions
2022-05-26 16:41:05 +00:00
Omar Lópezandkodiakhq[bot] 24a3bfdf78 Ensures json fields on each call (#2893)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-26 16:01:12 +00:00
Omar LópezandAlex van Andel 9710c6faa6 Cascades impersonations on user delete (#2891)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-05-26 15:48:02 +00:00
Hashen a0a4ff6ce6 remove unused imports (#2892) 2022-05-26 15:38:47 +00:00
Joe Au-YeungandAlex van Andel c67338bb3f Fix bug to check opt in bookings (#2889)
* Fix bug to check opt in bookings

* Implemented value, onChange instead of form access

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-05-26 16:24:19 +01:00
Hariom BalharaandAlex van Andel 727c6f723e Check in middleware to ensure authorization for all endpoints (#2885)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-05-26 13:52:25 +00:00
Hariom Balhara 40c81ee405 Fix 500 errors if data is invalid (#2886) 2022-05-26 08:38:10 +00:00
7c3090bc23 Feat/Display location information publicly (#2752)
* Updating checkbox field to reflect new designs

* Include Infobadge option checkbox

* Checkbox Field + i18n

* Default checked - true

* Sync with router

* Extracting Types

* Update filtering logic

* Add UI to booking page

* Default address/link

* Update hashedlink page

* Tidy up

* Video icon

* Add nullish check

* Update to use RHF controller

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-25 14:34:08 -06:00
Omar López 1bd6d03352 Webhook sec fixes (#2883)
* Webhook sec fixes

* Revert changes
2022-05-25 13:31:29 -06:00
Omar López 058550ba3d Webhook sec fixes (#2883)
* Webhook sec fixes

* Revert changes
2022-05-25 19:26:42 +00:00
Alex van Andel 6c248bb9f0 Fix deleted event when on different cal (404) (#2882) 2022-05-25 11:12:49 -06:00
29b09ce85d Resolve if can't find event on google cal (#2860)
* Resolve if can't find event on google cal

* Change order of  console.error

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

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-25 11:12:49 -06:00
Carina Wollendorfer ba4d2425cf Fix/email shows false (#2879) 2022-05-25 17:42:08 +01:00
Alex van Andel 49b361ab9c Fix deleted event when on different cal (404) (#2882) 2022-05-25 10:40:33 -06:00
sean-brydonandPeer Richelsen 1bf009f5f5 Disable Impersonation Option (#2880)
* Disable Impersonation

* Update Description Copy

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-25 15:21:18 +00:00
e16eee68b0 Resolve if can't find event on google cal (#2860)
* Resolve if can't find event on google cal

* Change order of  console.error

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

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-25 08:29:38 -06:00
Carina WollendorferandCarinaWolli 07aa2e88a1 fixes issues with date of recurring events on booking detail page (#2872)
* fixes date not showing for confirmed recurring event

* only show all events on upcoming bookings

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-05-25 11:24:01 +02:00
zomars dc934ed8ca Fixes paid bookings 2022-05-24 19:31:29 -06:00
Omar López 29bb6711bb sec-001 fixes (#2866)
* sec-001 fixes

* Typo
2022-05-24 19:31:29 -06:00
Alex van Andel 94966b1643 Hotfix: Fixing Security Issues (#2848) (#2850) 2022-05-24 19:31:29 -06:00
Omar López 7548233a96 sec-001 fixes (#2866) (#2867)
* sec-001 fixes (#2866)

* sec-001 fixes

* Typo

* Fixes paid bookings

# Conflicts:
#	apps/web/ee/components/stripe/Payment.tsx
2022-05-24 19:29:29 -06:00
zomars 9fba3f3767 Fixes paid bookings
# Conflicts:
#	apps/web/ee/components/stripe/Payment.tsx
2022-05-24 19:23:47 -06:00
Omar López db784f8333 sec-001 fixes (#2866)
* sec-001 fixes

* Typo
2022-05-24 18:25:14 -06:00
zomars a48a164e71 Submodule sync 2022-05-24 17:11:41 -06:00
Peer RichelsenandJoe Au-Yeung eacfdac016 fixed labels for seats, removes shadow from event-type inputs (#2862)
* fixed labels for seats

* minor fixes, removed shadow from all event-type inputs

* Render input field on pro accounts (#2859)

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2022-05-24 22:15:53 +02:00
323524b77c Redesign help menu item (#2710)
* Seperate help menu item and contact menu item

* Add menu items

* Install react-popover

* Render contact only if support keys are present

* Adjust contact support links

* Add translations

* Add embed changes

* Adjust menu if helped is pressed

* Add items to help menu

* Change button color on selection

* Create endpoint

* Create feedback table

* Create migration file

* Write feedback to db

* Remove logs

* Add response message

* Send feedback email

* Disable submit if no rating and after submit

* Add translations

* Fix padding

* Clean up

* Clean up

* Add user feedback email to .env example

* Lint fixes and styles

* Changed onClick function to a named function and fix style

* Fix ids order

* Removed commented code and changed textarea id and name

* Fix id orders

* Change to AND operator

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

* Add user relation to feedback

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

* Add migration files

* Change rating to strings

* Change rating to strings

* Fix type errors

* WIP success & error messages

* Change success and error to boolans

* Style messages

* Add await

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

* Remove duplicate string

* Refactor import statement

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

* Change opacity of emojis

* added support@cal.com email for feedback

* Add success toast

* Update .env.example

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

* Add tCRP route

* tCRP send email

* tCRP send email

Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-24 13:29:39 +00:00
c8d6c0dbdd Add seats to event types (#2485)
* Add seatsPerTimeSlot to event type schema

* Add seats per time slot to event type form

* Book event and render seats

* Pass booking uid for seats

* Disable requires confirmation if seats are enabled

* Fix type errors

* Update submodules

* Fix type errors

* Fix type errors

* Fix duplicate string

* Fix duplicate string

* Fix schema and migration file

* Fix render seats

* Fix bookinguid typos

* Remove console.log

* Fix type error

* Fix mobile formatting

* Update apps/web/lib/hooks/useSlots.ts

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

* Update apps/web/lib/hooks/useSlots.ts

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

* Added translation for seats available text

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-05-24 15:19:12 +02:00
zomars cc0ea19420 Makes sure to hash post-install cache 2022-05-23 13:59:03 -06:00
zomars a551aa0089 Updates submodules 2022-05-23 13:58:47 -06:00
Hariom Balhara 0ed0891d97 Fix infinite renders on event-type edit page (#2820) 2022-05-23 12:13:54 -06:00
Hariom Balhara f0a36f8194 Fix infinite renders on event-type edit page (#2820) 2022-05-23 18:59:09 +01:00
Alex van Andel 23576038aa Hotfix: Fixing Security Issues (#2848) (#2850) 2022-05-23 18:56:42 +01:00
zomars 0c44d9e0a6 Playwright binaries shouldn't be on deps 2022-05-23 11:46:15 -06:00
zomars 67770bf878 Playwright binaries shouldn't be on deps 2022-05-23 11:44:53 -06:00
+21 81d33b51bc v1.6.1 (#2851)
* 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 b5e40062d71157ca1d9384fb1f5c30d50625809d.

* 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>
2022-05-23 11:42:29 -06:00
zomars 162c6c10a8 Playwright binaries shouldn't be on deps 2022-05-23 11:25:34 -06:00
Arun Kumarandzomars 7d7a1c5bbc 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>
2022-05-23 17:20:35 +00:00
Omar López 53a7b1cc37 Update vital.json 2022-05-23 10:00:52 -06:00
Omar López f9f4a5aeb9 Update vital.json 2022-05-23 10:00:16 -06:00
zomars 066fce9d73 Merge branch 'production' 2022-05-23 09:30:47 -06:00
zomars 3a1d8b8183 Merge production to main 2022-05-23 09:29:19 -06:00
38f23986ad 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>
2022-05-23 11:29:29 +00:00
27422c351c 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>
2022-05-23 13:17:00 +02:00
c5ad74f61c 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>
2022-05-23 13:12:41 +02:00
0eff265682 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>
2022-05-23 11:54:24 +01:00
2560b72596 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>
2022-05-23 12:34:11 +02:00
sean-brydonandkodiakhq[bot] 7d7c61a1ab Adding labels (#2783)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-23 09:11:04 +01:00
c92283f03d 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>
2022-05-22 14:31:59 +02:00
Leo Giovanetti ba9be4e69d Fix login submit (#2849) 2022-05-22 14:28:18 +02:00
zomars b334586c3b Relocates admin to console 2022-05-20 17:40:51 -06:00
zomars a38c76e279 Relocates admin to console
# Conflicts:
#	apps/admin
2022-05-20 17:40:51 -06:00
github-actions[bot]andCrowdin Bot 6973d26153 New Crowdin translations by Github Action (#2791)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-05-20 16:42:47 +02:00
Syed Ali Shahbaz e62af90619 fixes dynamic color and typefix for tfunction after react upgrade (#2821) 2022-05-20 07:06:21 +00:00
Hariom Balhara e72cceff81 Fix auto-select and close of dropdown (#2819) 2022-05-19 08:50:17 +01:00
Ken Miller 1c296fb2d5 adding organizer as attendee to google calendar events (#2779) 2022-05-18 15:09:15 -06:00
zomars 1de50bd093 Update check-types.yml 2022-05-18 15:08:58 -06:00
+3 4b6c701c48 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>
2022-05-18 15:05:49 -06:00
Hariom Balharaandkodiakhq[bot] 463aff0702 Add vscode tasks.json (#2801)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-18 17:07:43 +00:00
zomars c1cc93c164 Consolidates test-results 2022-05-18 10:54:36 -06:00
6148e89425 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>
2022-05-18 16:25:30 +00:00
a7f7b29bae 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>
2022-05-18 16:15:27 +00:00
Joe Au-Yeung afafd45223 Show success page if booking was deleted on calendar (#2808)
* Add exception to 410

* Fix type error

* Add GoogelCalError type
2022-05-18 10:13:01 -06:00
iamkunandOmar López 31aff64e42 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>
2022-05-18 10:05:25 -06:00
952a149604 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>
2022-05-18 09:35:54 -06:00
Joe Au-Yeung 3aa7387ff5 Show success page if booking was deleted on calendar (#2808)
* Add exception to 410

* Fix type error

* Add GoogelCalError type
2022-05-18 09:34:21 -06:00
Joe Au-YeungandOmar López f8007a4f01 Add import statement (#2812)
* Add import statement

* Update apps/docs/next.config.js

Co-authored-by: Omar López <zomars@me.com>
2022-05-18 09:11:28 -06:00
Hariom Balhara 1fb637d29d Fix missing tabs - Embed (#2804)
* Fix missing tabs

* Fix Eslint error

* Fix Eslint errors
2022-05-18 09:04:50 -06:00
sean-brydon 80954aa866 Add team Id to hash url (#2803) 2022-05-18 08:59:50 -06:00
sean-brydon 821105dccb Add team Id to hash url (#2803) 2022-05-18 08:59:23 -06:00
zomars 5a124cec72 Ci updates 2022-05-17 22:30:16 -06:00
zomars 86a4037088 Update e2e.yml 2022-05-17 22:23:29 -06:00
zomars faa2805ccc Linting 2022-05-17 22:11:06 -06:00
zomars ca3e309ad9 Revert "Linting"
This reverts commit 1b59dacd64.
2022-05-17 22:09:06 -06:00
zomars fc59338057 Revert "Linting"
This reverts commit adf817766e.
2022-05-17 22:09:06 -06:00
zomars 2d56096d8f Update e2e.yml 2022-05-17 22:05:24 -06:00
zomars 8545bfef67 Update DatePicker.tsx 2022-05-17 22:02:23 -06:00
zomars 8a9b7ca78c Update e2e.yml 2022-05-17 21:27:29 -06:00
zomars c910b41043 Cache testing 2022-05-17 21:24:47 -06:00
zomars d51aade461 Update ci.yml 2022-05-17 20:22:33 -06:00
zomars fa837a5d94 Prisma fixes 2022-05-17 19:54:50 -06:00
zomars 1b59dacd64 Linting 2022-05-17 16:13:20 -06:00
zomars adf817766e Linting 2022-05-17 16:02:31 -06:00
zomars 98d7d7a11c Reusable inferSSRProps 2022-05-17 16:01:58 -06:00
zomars 4fa380d7ed Update admin 2022-05-17 15:36:04 -06:00
zomars 39031123e1 Update api 2022-05-17 15:36:01 -06:00
zomars b718718bd6 Update ci.yml 2022-05-17 15:35:58 -06:00
zomars 0153bc583a Type fixes 2022-05-17 15:32:54 -06:00
zomars fbbda20792 Reintroduces typescript-eslint
Buckle up!
2022-05-17 13:53:29 -06:00
zomars ef8018d2f7 Update ci.yml 2022-05-17 13:41:27 -06:00
zomars 46cd74b0b8 Create ci.yml 2022-05-17 13:40:33 -06:00
depfu[bot]andzomars db01f7be19 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>
2022-05-17 19:31:49 +00:00
zomars 25c550de3a Fixes infinite loop 2022-05-17 11:11:10 -06:00
zomars 683e8c4490 Fixes infinite loop 2022-05-17 11:10:57 -06:00
zomars a0296b8ccb Fixes infinite loop 2022-05-17 11:04:02 -06:00
zomars 6771960e9e Fixes infinite loop 2022-05-17 11:03:40 -06:00
zomars 1c4d2fae56 Fixes infinite loop 2022-05-17 10:53:10 -06:00
zomars 3b1dff3b83 Fixes infinite loop 2022-05-17 10:52:59 -06:00
Hariom BalharaandBailey Pumfleet 84967e5cdf Add login event (#2784)
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-05-17 17:22:57 +01:00
Ankit Gordhandasandkodiakhq[bot] 6b758eeffa fixing the hyperlink for open startup (#2777)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-17 16:03:05 +00:00
Alex van Andelandsean-brydon 8265c65e4c 2FA submit disabled (#2790) (#2792)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-05-17 18:55:16 +05:30
sean-brydon 676268b62d 2FA submit disabled (#2790) 2022-05-17 14:15:57 +01:00
zomars 9921c43c3f Update [user].ts 2022-05-16 17:24:29 -06:00
Omar López fc13a22162 Merge pull request #2772 from calcom/main
v1.6
2022-05-16 16:06:31 -06:00
zomars 6e2cb4f90d v1.6 2022-05-16 15:57:29 -06:00
zomars 8b0c7f579a v1.6 2022-05-16 15:55:16 -06:00
ad126efee8 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>
2022-05-16 14:20:09 -06:00
4e97d3ed72 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>
2022-05-16 13:20:17 -06:00
zomars 57dee30526 Update crowdin.yml 2022-05-16 13:11:12 -06:00
iamkunandkodiakhq[bot] 0bfdf2f8ef 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>
2022-05-16 19:03:33 +00:00
81d917e27a 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>
2022-05-16 18:50:53 +00:00
Omar López 3421af10a9 Apply suggestions from code review 2022-05-16 12:36:26 -06:00
Omar López f75917d9c4 Update apps/web/pages/apps/categories/[category].tsx 2022-05-16 12:26:19 -06:00
zomars c05040795d Typo 2022-05-16 12:20:40 -06:00
zomars 7d1b13c88d Typo 2022-05-16 12:17:28 -06:00
Omar López f8785e78a5 Merge branch 'production' into main 2022-05-16 12:02:27 -06:00
zomars 9bd1246ada 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
2022-05-16 12:00:04 -06:00
60c97c9fa9 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>
2022-05-16 17:57:53 +00:00
9c52e195ea 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>
2022-05-16 17:44:44 +00:00
HashenandSyed Ali Shahbaz e4c5df7a2e 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>
2022-05-16 11:26:32 -06:00
GitStartandgitstart 2eae40eff2 Readd steps to create a new user #2665 (#2759)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
2022-05-16 11:23:40 -06:00
9e662aa202 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>
2022-05-16 17:15:09 +00:00
f2a6d00348 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>
2022-05-16 10:27:36 -06:00
7fd149fd2e 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>
2022-05-16 09:50:12 -06:00
zomars 3c37c2e774 Skip sending emails in E2E 2022-05-16 09:49:44 -06:00
GitStartandgitstart 84f19bb4df Bad UX when user wants to set the default Event Type Title #2245 (#2760)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
2022-05-16 09:44:19 -06:00
Agusti Fernandez PardoandAgusti Fernandez Pardo 27540b09ce fix: remove hardcoded redirect in signin url email verification (#2764)
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-05-16 09:38:45 -06:00
Agusti Fernandez PardoandAgusti Fernandez Pardo 549e38fb78 fix: remove hardcoded redirect in signin url email verification (#2764)
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-05-16 17:34:13 +02:00
Hariom Balhara 2655f816af Fix white border (#2761) 2022-05-16 12:38:22 +00:00
Mitchell Mooreandzomars 016023fa50 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>
2022-05-15 03:41:39 +00:00
2a53614723 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 b5e40062d71157ca1d9384fb1f5c30d50625809d.

* Reverts some changes

* Renames isLoading

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
2022-05-14 12:47:23 -06:00
Leo GiovanettiandOmar López 212fd1bc14 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>
2022-05-14 13:49:39 +00:00
GitStartandgitstart 93bd4ed79e Sign in button should be changed or disabled after click #2654 (#2749)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
2022-05-14 07:38:56 -06:00
Omar López 2bb6f33112 E2E fixtures (#2747) 2022-05-13 21:02:10 -06:00
Omar Lópezandkodiakhq[bot] 68e275ab07 Improving Email DRYness (#2486)
* Email DRY

* WIP

* Improve email DRYness

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-13 12:34:54 +00:00
498759890f 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>
2022-05-13 14:20:50 +02:00
zomars bf1f9b6a32 Build fixes 2022-05-12 13:54:46 -06:00
Joe Au-Yeung b69c1e46d1 Merge pull request #2745 from calcom/apps/multiple-categories
Allow apps to belong to multiple categories
2022-05-12 11:47:11 -06:00
zomars dcd2862e72 Increases timeout temporarily 2022-05-12 08:16:27 -06:00
7a08403636 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>
2022-05-12 09:33:15 +00:00
574f9cb5d4 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>
2022-05-12 10:21:46 +01:00
zomars 613e42ce70 E2E fixing attemps 2022-05-11 21:07:22 -06:00
zomars 43f479299a Fixes delete-me test 2022-05-11 20:21:16 -06:00
zomars ecb3264b52 getBusyTimes consolidation 2022-05-11 19:49:21 -06:00
zomars e89520d7d6 Update testUtils.ts 2022-05-11 16:55:30 -06:00
zomars 892bb90d9d Update testUtils.ts 2022-05-11 16:39:45 -06:00
zomars a2ea08ba5f TODO marks blocking test to be fixed 2022-05-11 16:29:44 -06:00
50632dd9dd 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>
2022-05-11 20:47:42 +00:00
zomars 06702aa48a E2E and paid bookings fixes 2022-05-11 14:36:38 -06:00
zomars f2988870d5 Update booking-pages.test.ts 2022-05-11 12:03:22 -06:00
zomars f0ea8d30ca Parallelizes some tests 2022-05-11 11:19:22 -06:00
zomars c3909ccc70 Multiple E2E improvements 2022-05-11 10:46:52 -06:00
Hariom Balhara ae15a7d739 Fix time issue 2022-05-11 09:53:16 -06:00
Syed Ali Shahbazandkodiakhq[bot] 01e88b3807 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>
2022-05-11 14:14:08 +00:00
Hariom BalharaandOmar López effb9d56d9 Fix preview.html not built and thus served during depooy (#2727)
Co-authored-by: Omar López <zomars@me.com>
2022-05-11 14:01:49 +00:00
Leo Giovanetti 2a5a89fe50 Missing fix for success page 2022-05-11 07:50:30 -06:00
2ce1e78053 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>
2022-05-11 07:50:30 -06:00
3bbbc80511 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>
2022-05-11 10:12:59 -03:00
Hariom BalharaandPeer Richelsen 19128fb08e 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>
2022-05-11 10:35:43 +00:00
Peer Richelsen 0945bbe5cf fixes #2732 (#2732) 2022-05-11 12:04:04 +02:00
Peer Richelsen ced6975fc8 added giphy description (#2730) 2022-05-11 11:46:27 +02:00
fb436996c0 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>
2022-05-11 07:59:49 +00:00
Hariom Balharaandkodiakhq[bot] 50f1fe544e 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>
2022-05-11 05:41:09 +00:00
Syed Ali Shahbaz 746643bf8e adds availability select loader (#2718) 2022-05-11 05:26:06 +00:00
Hariom BalharaandPeer Richelsen 65a69ef1e4 Add more embed events (#2719)
* Add more embed events

* Add more embed events

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-11 05:14:08 +00:00
6483182ef6 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>
2022-05-11 04:58:10 +00:00
zomars 784a91709c Update dynamic-booking-pages.test.ts 2022-05-10 22:46:22 -06:00
zomars 82a52e065f More test fixes 2022-05-10 22:28:48 -06:00
zomars a1f6738cf1 Update playwright.config.ts 2022-05-10 21:51:24 -06:00
zomars a231945842 Test fixes 2022-05-10 21:37:09 -06:00
zomars a507d5963c Type fixes 2022-05-10 21:35:44 -06:00
Peer Richelsen 92806d5257 fixed /booking skeleton (#2722)
* fixed /booking skeleton

* nit
2022-05-10 16:59:23 +02:00
Hariom Balhara 2d6d1cb444 Hotfix: Success page layout broken due to duplicate "When" (#2716) 2022-05-10 14:49:46 +05:30
Hariom Balhara ef68f4f4f8 Fix preview.html not built and thus served during depooy (#2713) 2022-05-10 10:54:20 +05:30
zomars 9440df4445 Make apps single pages public 2022-05-09 16:17:23 -06:00
zomars 4e0efb76cd Turbo fixes 2022-05-09 16:17:23 -06:00
zomars 18c28cc3fd Make apps single pages public 2022-05-09 16:17:07 -06:00
zomars d40e8caff9 Turbo fixes 2022-05-09 16:08:03 -06:00
zomars 3161cc4d45 Merge branch 'main' into production 2022-05-09 14:58:33 -06:00
Omar López ed808c3be6 Merge pull request #2672 from calcom/main
v1.5.4
2022-05-09 14:56:23 -06:00
zomars 4099a477d1 v1.5.4 2022-05-09 14:33:32 -06:00
zomars 6542da7e30 Formatting 2022-05-09 14:33:13 -06:00
Hariom Balharaandkodiakhq[bot] 8336611f54 Missing translation (#2697)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-09 22:24:01 +02:00
zomars 819c6c96e8 Build fixes 2022-05-09 14:05:57 -06:00
zomars e9ff358ac2 Update BookingPage.tsx 2022-05-09 13:59:45 -06:00
zomars f79fd36c03 Merge branch 'production' 2022-05-09 13:58:18 -06:00
Alex van Andelandkodiakhq[bot] edd99cdeb2 Replaces member avatars with links to avatar.png endpoint (#2708)
* Replaces member avatars with links to avatar.png endpoint

* Replaced additional occurences

* Use WEBSITE_URL from @calcom/lib/constants instead

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-09 18:25:12 +00:00
Hariom Balharaandkodiakhq[bot] 9825754b32 Hotfix: blank page for booking embed in Incognito Chrome (#2700)
* Merge remote-tracking branch 'origin/main' into feat/success-url

* Fix localstorage access

* Fix Comments

* make custom eleemnt explicitly 100% in width to go full width in a flex type parent

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-09 18:12:47 +00:00
Omar López 6a18b40c97 Update package.json
Fixes v14 builds
2022-05-09 12:00:49 -06:00
Hariom Balhara d00f0bae1d All non recurring bookings were clubbed into one distinct booking (#2706) 2022-05-09 16:04:42 +00:00
sean-brydonandPeer Richelsen fb042a36b6 Fix Mobile UI for /settings/security (#2703)
* Fix Mobile UI

* Remove truncate

* Move buttons

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-09 13:43:27 +02:00
Peer Richelsen 6c27b04f83 fixed team dark mode (#2702) 2022-05-09 11:25:36 +00:00
Syed Ali Shahbazandkodiakhq[bot] 9322b4ab4c Flow, UX and other improvements for hash my url feature (#2644)
* added toast feedback

* updated flow

* locale

* updated locale data

* removed unused booking call for reschedule flow

* fixed hashedURL test

* test adjustment

* further test changes

* added check in test to click check only if unchecked

* Added private link quick copy button

* fixed spacing

* fix lint

* consistency

* moved create hash function out of component render

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-09 16:41:07 +05:30
Peer Richelsen 5464d4c010 Update README.md 2022-05-09 12:33:39 +02:00
351622c4a2 Add Invalid Email Error (#2637)
* Add error message UI

* Add border color

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-05-09 08:24:39 +00:00
Hariom Balhara 44736ac461 Fix turbo caching (#2695)
* Fix turbo cachin

* Improve tests stability
2022-05-07 08:54:30 +00:00
Hariom Balhara a2da95b12b Fix app (#2694) 2022-05-07 05:56:29 +00:00
zomars 8ae5b68504 Adds hint to input 2022-05-06 16:09:40 -06:00
zomars 1a3c3af072 Tooltip export fixes 2022-05-06 15:59:15 -06:00
zomars 5dde542952 Form fixes 2022-05-06 15:46:31 -06:00
zomars 4922a13b68 Form warning fixes 2022-05-06 15:44:57 -06:00
zomars a05860515e Form fixes 2022-05-06 15:44:51 -06:00
zomars 7399d6421e Form warning fixes 2022-05-06 15:30:46 -06:00
iamkunandkodiakhq[bot] 269dea70a1 fix: Booking page display time based on selected timezone (#2691)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-06 19:28:09 +00:00
alannncandkodiakhq[bot] 46690fa72b fix/scroll-mobile-on-dialog (#2693)
* Added mobile first css props for scroll

* Fix submodule previous add

* Fix mobile first css for dialog

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-06 18:34:48 +00:00
8c9096b55b Vital App - Auto reschedule based on health data (#2500)
* Add vital integration

* Tidy up client_user_id creation

* Rename vital app to vitalother to follow name rules

* Added env var

* App vital reschedule

* Fix on app structure and api calls

* Implemented user identification from webhook

* WIP fix api call and read me

* Save vital settings via api

* Now saving userVitalSettings and trigger reschedule on selected param

* Added translations

* Fix type for vitalSettings

* Using api to get env vars required for url, fix display of vital settings

* Fix hours placeholder, translation not working

* Renames vital app

* Update seed-app-store.ts

* Update package.json

* Update yarn.lock

* Refactored env variables

* Update README.md

* Migrates to api_keys

* Extracts AppConfiguration

* vitalClient fixes

* Update index.ts

* Update metadata.ts

* Update index.ts

* Update metadata.ts

* Added namespace vital for translations

Co-authored-by: Maitham <maithamdib@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-06 17:21:30 +00:00
Hariom Balharaandkodiakhq[bot] 67cc3a6409 Embed Code Generator: Fix Preview HTML and Embed Lib path for production (#2688)
* Improve logging

* Improve logging

* Keep embed origin conigurable

* Make embed URL and embed origin conigurable through env

* Gitignore public embed

* Add fingerprint to preview as well

* Fix path

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-06 15:56:26 +00:00
83ec6d69eb fix query to list API keys (#2690)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-06 14:48:27 +00:00
Joe Au-Yeung 65a76b96c6 Add missing info to success page (#2680)
* Add missing info to success page

* Remove console.logs

* Add localized format to time
2022-05-06 08:15:05 -06:00
Hariom Balhara dd7f22e021 Fix border in dark theme (#2687) 2022-05-06 12:09:12 +02:00
zomars 1a06d9906b Fixes daily-video slug 2022-05-05 16:36:30 -06:00
zomars 6fb301970b Fixes daily-video slug 2022-05-05 16:34:26 -06:00
zomars 3baf7060f7 Adds console url to redirection whitelist 2022-05-05 16:29:38 -06:00
zomars 0b82b85166 Adds console url to redirection whitelist 2022-05-05 16:29:17 -06:00
zomars 82dfd807c8 All apps in DB are installed by default 2022-05-05 16:21:52 -06:00
zomars af9612968b Fixes missing slack credentials 2022-05-05 16:21:52 -06:00
zomars 70455f56a2 All apps in DB are installed by default 2022-05-05 16:18:28 -06:00
zomars f839fd2bb4 Fixes missing slack credentials 2022-05-05 16:15:40 -06:00
1a79e0624c Recurring Events (#2562)
* Init dev

* UI changes for recurring event + prisma

* Revisiting schema + changes WIP

* UI done, BE WIP

* Feature completion

* Unused query param removed

* Invalid comment removed

* Removed unused translation

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

Thanks!

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

* Success page changes

* More progress

* Email text tweaks + test + seed

* Tweaking emails + Cal Apps support WIP

* No app integration for now
Final email and pages tweaks to avoid recurring info showed

* Missing comment for clarity

* Yet again, comment

* Last minute fix

* Missing tooltip for upcoming bookings

* Fixing seed

* Fixing import

* Increasing timeout for e2e

* Fixing any

* Apply suggestions from code review

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

* Update apps/web/pages/d/[link]/book.tsx

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

* Code improvements

* More code improvements

* Reverting back number input arrows

* Update BookingPage.tsx

* Update BookingPage.tsx

* Adds fallback for sendOrganizerPaymentRefundFailedEmail

* Type overkill

* Type fixes

* Type fixes

* Nitpicks

* Update success.tsx

* Update success.tsx

* Update success.tsx

* Fixing types

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>
2022-05-05 18:16:25 -03:00
iamkun 26e46ff06c fix: DatePicker to display the correct available hours (#2686) 2022-05-05 18:51:22 +00:00
71e67b50b2 Fix: Improve docs and mobile style of api docs (#2635)
* fix: adds servers in openapi, remove hack in snippets, update deps, make dynamic import to use latests swagger ui deps

* fix: remove unneded import

* fix: adds yarn dev commands for api and swagger

* fix: prisma not web before api

* fix: improve mobile docs api

* fix request snippets

* fix: custom more visible scrollbar for snippets in moible

* fix: remove comments and ugly scrollbar

* fix: types and remove lib url

* fix: install iframe-react-resizer in docs

* fix: remove web scope from yarn dev:api

* fix: remove json-schema autogenerated as wont be used

* fix: apiKeyAuth

* fix: swagger patch thx hariom

* fix: add api to env/example

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-05-05 09:36:02 -06:00
Hariom Balharaandkodiakhq[bot] 174ed9f6d1 Embed Snippet Generator (#2597)
* Add support to dynamically change the theme

* Add Embed UI in app

* Update UI as per Figma

* Dynamicaly update Embed Code

* Get differnet modes working in preview

* Support Embed on EventType Edit, Team Link Fix and Mobile unsupported

* Fix auto theme switch in Embed Snippet generator

* Fix types

* Self Review fixes

* Remove Embed from App section

* Move get query after the middleware to let middleware work on it

* Add sandboxes in the document

* Add error handling for embed loading

* Fix types

* Update snapshots and fix bug identified by tests

* UI Fixes

* Add Embed Tests

* Respond in preview to width and height

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-05 08:29:49 -06:00
Carina WollendorferandCarinaWolli 60146ed2c5 fix redirect to setup page (#2683)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-05-05 09:32:08 +00:00
alannncandzomars df7abdfc06 Fix/reschedule on dynamic events (#2657)
* Reschedule for dynamic events

* Fix lint

* Handling attendee calendar event cancellation

Co-authored-by: zomars <zomars@me.com>
2022-05-05 01:03:36 +00:00
zomars 6ec2b20a23 Separates appStore env file 2022-05-04 16:13:28 -06:00
zomars 09d0f68c4c Update package.json 2022-05-04 16:09:58 -06:00
zomars d6b7311c66 Build fixes 2022-05-04 15:28:58 -06:00
zomars f1a2239c97 Linting and legibility 2022-05-04 15:28:58 -06:00
zomars 977ad141ee Extracts useMeQuery to own hook 2022-05-04 15:28:58 -06:00
Julián Sánchez 06f88eb5a3 Move method that gets the current user to a separate file 2022-05-04 15:28:58 -06:00
Julián Sánchez daf39a4095 Fix problem related to data types 2022-05-04 15:28:58 -06:00
Julián David Sánchez Gallego 0973d79c31 Update way to get the 'accepted' attribute 2022-05-04 15:28:58 -06:00
Julián David Sánchez Gallego 257481bad5 Update restriction to change the role of other Owners 2022-05-04 15:28:58 -06:00
Julián David Sánchez Gallego 68e08f13a1 Change plain strings 2022-05-04 15:28:58 -06:00
Julián David Sánchez Gallego 3234898892 Add restrictions to protect the owners and change their roles 2022-05-04 15:28:58 -06:00
Joe Au-Yeungandkodiakhq[bot] 82f7779a23 MS Teams title to state that a work/school account is required. (#2677)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-04 14:45:30 -04:00
zomars bd9b83540b Jitsi slug fixes 2022-05-04 12:25:07 -06:00
zomars 4f0ee7b0b6 slug fixes 2022-05-04 12:25:07 -06:00
zomars 4437bfa840 Fix for undefined keys 2022-05-04 08:13:52 -06:00
zomars b9e34c99ca Jitsi slug fixes 2022-05-03 22:44:24 -06:00
zomars 71fa872d5c slug fixes 2022-05-03 22:38:48 -06:00
zomars 377857915f Space booking test 2022-05-03 22:36:07 -06:00
zomars ec5020ca3d Update index.ts 2022-05-03 22:30:46 -06:00
alannncandzomars f1bed08c13 feature/space-booking-app (#2673)
* Reschedule for dynamic events

* Fix lint

* feature/space-booking-app initial commit

* added loom video and fixes for main branch

* Revert previous commmit

* Renames spacebookingother to spacebooking

* Type and perf improvements

* Updated comment

* Update seed-app-store.ts

Co-authored-by: zomars <zomars@me.com>
2022-05-03 22:07:17 -06:00
alannncandzomars 2cb663cd6a feature/space-booking-app (#2673)
* Reschedule for dynamic events

* Fix lint

* feature/space-booking-app initial commit

* added loom video and fixes for main branch

* Revert previous commmit

* Renames spacebookingother to spacebooking

* Type and perf improvements

* Updated comment

* Update seed-app-store.ts

Co-authored-by: zomars <zomars@me.com>
2022-05-04 04:06:20 +00:00
fa1ca5fba0 Slack Signature Verification (#2667)
* Slack Verify

* Adding await

* Update slackVerify.ts

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-05-03 23:40:01 +00:00
02b935bcde Feat/zapier app (#2623)
* create basic app structure

* add zapierSubscription model to prisma.schema

* change column name triggerEvent to lower case

* add zapier functionality + enpoints + adjust prisma.schema

* add subscriptionType + refactor code

* add app store information

* create setup page to generate api key

* clean code

* add copy functionality in setup page

* clean code

* add apiKeyType and delte key when uninstalled or new key generated

* clean code

* use Promise.all

* only approve zapier api key

* clean code

* fix findValidApiKey for api keys that don't expire

* fix migrations

* clean code

* small fixes

* add i18n

* add README.md file

* add setup guide to README.md

* fix yarn.lock

* Renames zapierother to zapier

* Typo

* Updates package name

* Rename fixes

* Adds zapier to the App Store seeder

* Adds missing zapier to apiHandlers

* Adds credential relationship to App

* Rename fixes

* Allows tailwind to pick up custom app-store components

* Consolidates zapier_setup_instructions

* Webhook fixes

* Uses app relationship instead of custom type

* Refactors sendPayload to accept webhook object

Instead of individual parameters

* refactoring

* Removes unused zapier check

* Update cancel.ts

* Refactoring

* Removes example comments

* Update InstallAppButton.tsx

* Type fixes

* E2E fixes

* Deletes all user zapier webhooks on integration removal

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2022-05-03 23:16:59 +00:00
zomars b8d5c53813 Merge branch 'main' into production 2022-05-02 19:08:05 -06:00
zomars bb90fe0d4b Marks installed by default 2022-05-02 19:05:49 -06:00
zomars ba283e3dc0 Marks installed by default 2022-05-02 19:05:13 -06:00
zomars 9ae8a48dcd Type fixes 2022-05-02 18:21:29 -06:00
zomars 774f707c9f Type fixes 2022-05-02 18:21:09 -06:00
zomars a6417c5757 Moar fixes 2022-05-02 18:00:36 -06:00
zomars aebb610403 Moar fixes 2022-05-02 18:00:20 -06:00
zomars fdbfd759af App env fixes 2022-05-02 17:59:06 -06:00
zomars 0213f66eb6 App env fixes 2022-05-02 17:59:06 -06:00
zomars 1de385a410 App env fixes 2022-05-02 17:42:25 -06:00
zomars 6011b440a8 App env fixes 2022-05-02 17:31:07 -06:00
zomars de4b3c186e Hubspot fixes 2022-05-02 16:55:34 -06:00
zomars 4c8ff47ae7 Hubspot fixes 2022-05-02 16:50:52 -06:00
zomars 54269ba0bf v1.5.3 2022-05-02 16:21:11 -06:00
zomars 361579ba31 Migration seeder fixes 2022-05-02 16:21:11 -06:00
000785c29f Add Meta Mask to app store (#2650)
* Adds available apps

* Adds App Model

* WIP

* Create meta mask app folder

* Add description and images

* Remove credential from installed apps page

* Updates seeder script

* Seeder fixes

* lowercase categories

* Upgrades prisma

* WIP

* WIP

* Hopefully fixes circular deps

* Type fixes

* Fixes seeder

* Adds migration to connect Credentials to Apps

* Updates app store callbacks

* Updates google credentials

* Uses dirName from DB

* Type fixes

* Update reschedule.ts

* Seeder fixes

* Fixes categories listing

* Update index.ts

* Update schema.prisma

* Updates dependencies

* Renames giphy app

* Uses dynamic imports for app metadata

* Fixes credentials error

* Uses dynamic import for api handlers

* Dynamic import fixes

* Allows for simple folder names in app store

* Remove video adaptor

* Squashes app migrations

* seeder fixes

* Renames to metamask

* Updates metamask metadata

* Fixes dyamic imports

* Remove comments

* Create migration.sql

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-02 16:21:11 -06:00
Omar López 2e6bc5e5b4 Fixes/app store keys in db (#2651)
* Adds available apps

* Adds App Model

* WIP

* Updates seeder script

* Seeder fixes

* lowercase categories

* Upgrades prisma

* WIP

* WIP

* Hopefully fixes circular deps

* Type fixes

* Fixes seeder

* Adds migration to connect Credentials to Apps

* Updates app store callbacks

* Updates google credentials

* Uses dirName from DB

* Type fixes

* Update reschedule.ts

* Seeder fixes

* Fixes categories listing

* Update index.ts

* Update schema.prisma

* Updates dependencies

* Renames giphy app

* Uses dynamic imports for app metadata

* Fixes credentials error

* Uses dynamic import for api handlers

* Dynamic import fixes

* Allows for simple folder names in app store

* Squashes app migrations

* seeder fixes

* Fixes dyamic imports

* Update apiHandlers.tsx
2022-05-02 16:21:11 -06:00
Shrey Gupta 11f6972ec9 feat(app-store): Add Giphy app (#2580) 2022-05-02 16:21:11 -06:00
Peer Richelsen df801b4205 Update README.md 2022-05-02 16:21:11 -06:00
Peer Richelsen 433f2bafd8 Update index.mdx 2022-05-02 16:21:11 -06:00
Peer Richelsen c5fc1a4648 Update PULL_REQUEST_TEMPLATE.md 2022-05-02 16:21:11 -06:00
sean-brydonandkodiakhq[bot] 33d486b160 Enable Autocomplete (#2645)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
Peer Richelsen 7e57c192ee added animations for dialog and tooltip (#2648)
* added animations for dialog and tooltip

* Update .env.example
2022-05-02 16:21:11 -06:00
Joe Au-Yeungandkodiakhq[bot] e4f7e26ad5 Hotfix - change calendar error message (#2643)
* Change calendar error message

* Change calendar error message

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
89b4acdfaf Hash my url (#2484)
* disposable link model added

* disposable model updated

* added disposable slug availability page

* added disposable book page

* added disposable slug hook

* added disposable link booking flow

* updated schema

* checktype fix

* added checkfix and schema generated

* create link API added

* added one time link view on event type list

* adjusted schema

* fixed disposable visual indicator

* expired check and visual indicator added

* updated slug for disposable event type

* revised schema

* WIP

* revert desc

* revert --WIP

* rework based on change of plans

* further adjustments

* added eventtype option for hashed link

* added refresh and delete on update

* fixed update call conditions

* cleanup

* code improvement

* clean up

* Potential fix for 404

* backward compat for booking page

* fixes regular booking for user and team

* typefix

* updated path for Booking import

* checkfix

* e2e wip

* link err fix

* workaround for banner issue in event type update-test

* added regenerate hash check

* fixed test according to new testID

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-05-02 16:21:11 -06:00
alannncandkodiakhq[bot] d856ef53a7 Fix emails and cal event descriptions (#2634)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
3256d601db Fixes google meet url not appearing calendar invite (#2636)
* Update cal description

* Tidy up console logs

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>
2022-05-02 16:21:11 -06:00
Afzal Sayedandkodiakhq[bot] 938f4f2b4d Pass userId while creating event-type (#2599)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
sean-brydon 6999ab09b6 Fixing Scope Name [Slack] (#2641) 2022-05-02 16:21:11 -06:00
Peer Richelsen c2d52bcfd2 consistency for tablet booking page (#2640) 2022-05-02 16:21:11 -06:00
Peer Richelsen d1c37f84aa fixed layout in insalled apps (#2639) 2022-05-02 16:21:11 -06:00
sean-brydon 83f9defc65 Unlock edit on reschedule (#2628) 2022-05-02 16:21:11 -06:00
c4dbab2637 Add new response if request contains account id (#2629)
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
alannnc e76fafdccf Fix book event form schema validation (#2633) 2022-05-02 16:21:11 -06:00
sean-brydon 73e3e4e226 Adding validation for name and email (#2612) 2022-05-02 16:21:11 -06:00
6535d654d7 Add Event Types Test (#2610)
* Add Event Types Test

* Accept license for tests

* Accept license on preview

* Remove debugging code

* Add License consent flag

* Test fixes

* Update playwright.config.ts

* Update webhookResponse-chromium.txt

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-05-02 16:21:11 -06:00
Hariom Balhara a224a46654 Make sure that absolute URL is of WEBAPP only (#2624) 2022-05-02 16:21:11 -06:00
Peer Richelsen 59c0784cd6 Update PULL_REQUEST_TEMPLATE.md 2022-05-02 16:21:11 -06:00
Peer Richelsen b544d5f781 Update PULL_REQUEST_TEMPLATE.md 2022-05-02 16:21:11 -06:00
Hariom Balharaandsean-brydon ebf1373339 Reduce Payload for Event-Types[Avoid 500] (#2627)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
sean-brydon 75a07f527e Fix scope (#2625) 2022-05-02 16:21:11 -06:00
alannnc 4b75bf7cce Fix/login with provider (#2594) 2022-05-02 16:21:11 -06:00
e260ba0e49 Swagger docs improved (#2607)
* fix: adds servers in openapi, remove hack in snippets, update deps, make dynamic import to use latests swagger ui deps

* fix: remove unneded import

* fix: adds yarn dev commands for api and swagger

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
sean-brydonandPeer Richelsen 95dfb5b538 Loader Components (#2616)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-02 16:21:11 -06:00
7d3f070e27 Bug/email notes hidden (#2611)
* Fix Width

* Fixes email notes

* Fixing reschedule email

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-05-02 16:21:11 -06:00
Hariom Balhara 01fdbaa990 Improve logs for 500 error in datadog for /book/event (#2593) 2022-05-02 16:21:11 -06:00
Hariom Balhara 05acd26efe Add debugging details (#2585) 2022-05-02 16:21:11 -06:00
1421b9c0af Feat/impersonate users (#2503)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-05-02 16:21:11 -06:00
sean-brydonandOmar López 6197ae25c6 Fix providerName (#2589)
Co-authored-by: Omar López <zomars@me.com>
2022-05-02 16:21:11 -06:00
zomars de0883b14b v1.5.3 2022-05-02 16:07:45 -06:00
zomars acc6db901c Migration seeder fixes 2022-05-02 16:02:45 -06:00
7f463830bd Add Meta Mask to app store (#2650)
* Adds available apps

* Adds App Model

* WIP

* Create meta mask app folder

* Add description and images

* Remove credential from installed apps page

* Updates seeder script

* Seeder fixes

* lowercase categories

* Upgrades prisma

* WIP

* WIP

* Hopefully fixes circular deps

* Type fixes

* Fixes seeder

* Adds migration to connect Credentials to Apps

* Updates app store callbacks

* Updates google credentials

* Uses dirName from DB

* Type fixes

* Update reschedule.ts

* Seeder fixes

* Fixes categories listing

* Update index.ts

* Update schema.prisma

* Updates dependencies

* Renames giphy app

* Uses dynamic imports for app metadata

* Fixes credentials error

* Uses dynamic import for api handlers

* Dynamic import fixes

* Allows for simple folder names in app store

* Remove video adaptor

* Squashes app migrations

* seeder fixes

* Renames to metamask

* Updates metamask metadata

* Fixes dyamic imports

* Remove comments

* Create migration.sql

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-05-02 15:44:37 -06:00
Omar López 6a27fb2959 Fixes/app store keys in db (#2651)
* Adds available apps

* Adds App Model

* WIP

* Updates seeder script

* Seeder fixes

* lowercase categories

* Upgrades prisma

* WIP

* WIP

* Hopefully fixes circular deps

* Type fixes

* Fixes seeder

* Adds migration to connect Credentials to Apps

* Updates app store callbacks

* Updates google credentials

* Uses dirName from DB

* Type fixes

* Update reschedule.ts

* Seeder fixes

* Fixes categories listing

* Update index.ts

* Update schema.prisma

* Updates dependencies

* Renames giphy app

* Uses dynamic imports for app metadata

* Fixes credentials error

* Uses dynamic import for api handlers

* Dynamic import fixes

* Allows for simple folder names in app store

* Squashes app migrations

* seeder fixes

* Fixes dyamic imports

* Update apiHandlers.tsx
2022-05-02 14:39:35 -06:00
Shrey Gupta 21867c9cd4 feat(app-store): Add Giphy app (#2580) 2022-05-01 21:42:35 +01:00
Peer Richelsen 276821e0b5 Update README.md 2022-04-30 15:55:25 +02:00
Peer Richelsen 8028b1ddad Update index.mdx 2022-04-30 15:54:57 +02:00
Peer Richelsen 5abbd818d3 Update PULL_REQUEST_TEMPLATE.md 2022-04-30 14:51:23 +02:00
sean-brydonandkodiakhq[bot] 43944a7d31 Enable Autocomplete (#2645)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-29 17:28:34 +00:00
Peer Richelsen 8bdc137917 added animations for dialog and tooltip (#2648)
* added animations for dialog and tooltip

* Update .env.example
2022-04-29 17:17:34 +00:00
Joe Au-Yeungandkodiakhq[bot] 02fb15228b Hotfix - change calendar error message (#2643)
* Change calendar error message

* Change calendar error message

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-28 20:45:27 +00:00
59a1db9068 Hash my url (#2484)
* disposable link model added

* disposable model updated

* added disposable slug availability page

* added disposable book page

* added disposable slug hook

* added disposable link booking flow

* updated schema

* checktype fix

* added checkfix and schema generated

* create link API added

* added one time link view on event type list

* adjusted schema

* fixed disposable visual indicator

* expired check and visual indicator added

* updated slug for disposable event type

* revised schema

* WIP

* revert desc

* revert --WIP

* rework based on change of plans

* further adjustments

* added eventtype option for hashed link

* added refresh and delete on update

* fixed update call conditions

* cleanup

* code improvement

* clean up

* Potential fix for 404

* backward compat for booking page

* fixes regular booking for user and team

* typefix

* updated path for Booking import

* checkfix

* e2e wip

* link err fix

* workaround for banner issue in event type update-test

* added regenerate hash check

* fixed test according to new testID

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-04-28 09:44:26 -06:00
alannncandkodiakhq[bot] 8e956893ca Fix emails and cal event descriptions (#2634)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-28 15:05:29 +00:00
d960e03acf Fixes google meet url not appearing calendar invite (#2636)
* Update cal description

* Tidy up console logs

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>
2022-04-28 14:54:31 +00:00
Afzal Sayedandkodiakhq[bot] 99666440cf Pass userId while creating event-type (#2599)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-28 07:56:10 -06:00
sean-brydon f274c0bde3 Fixing Scope Name [Slack] (#2641) 2022-04-28 12:42:37 +00:00
Peer Richelsen d1082e55a4 consistency for tablet booking page (#2640) 2022-04-28 13:31:55 +02:00
Peer Richelsen af0d1980c6 fixed layout in insalled apps (#2639) 2022-04-28 13:22:40 +02:00
sean-brydon a6183e0ccf Unlock edit on reschedule (#2628) 2022-04-28 09:10:40 +00:00
eea40c69f7 Add new response if request contains account id (#2629)
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-27 21:34:04 +00:00
alannnc 13ae773868 Fix book event form schema validation (#2633) 2022-04-27 23:21:18 +02:00
sean-brydon 6f0fcc9d1b Adding validation for name and email (#2612) 2022-04-27 16:19:04 +01:00
7d98c0bb1c Add Event Types Test (#2610)
* Add Event Types Test

* Accept license for tests

* Accept license on preview

* Remove debugging code

* Add License consent flag

* Test fixes

* Update playwright.config.ts

* Update webhookResponse-chromium.txt

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-04-27 08:58:04 -06:00
Hariom Balhara 82d77dc10f Make sure that absolute URL is of WEBAPP only (#2624) 2022-04-27 16:28:36 +02:00
Peer Richelsen ae1f35f515 Update PULL_REQUEST_TEMPLATE.md 2022-04-27 14:16:31 +02:00
Peer Richelsen 66f3fd2e07 Update PULL_REQUEST_TEMPLATE.md 2022-04-27 14:15:44 +02:00
Hariom Balharaandsean-brydon cf346f6aa3 Reduce Payload for Event-Types[Avoid 500] (#2627)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2022-04-27 11:08:13 +00:00
sean-brydon 34d3aac4b0 Fix scope (#2625) 2022-04-27 16:27:39 +05:30
alannnc c22b6ca670 Fix/login with provider (#2594) 2022-04-26 15:12:08 +00:00
fa1b29a99f Swagger docs improved (#2607)
* fix: adds servers in openapi, remove hack in snippets, update deps, make dynamic import to use latests swagger ui deps

* fix: remove unneded import

* fix: adds yarn dev commands for api and swagger

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-26 15:00:53 +00:00
sean-brydonandPeer Richelsen d61238c832 Loader Components (#2616)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-04-26 14:11:02 +00:00
28b432058a Bug/email notes hidden (#2611)
* Fix Width

* Fixes email notes

* Fixing reschedule email

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-26 13:46:20 +00:00
Hariom Balhara 4360ada3e4 Improve logs for 500 error in datadog for /book/event (#2593) 2022-04-26 11:31:57 +00:00
Hariom Balhara 5336bf3fe2 Add debugging details (#2585) 2022-04-26 11:15:57 +00:00
6d5db1cb3a Feat/impersonate users (#2503)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>
2022-04-26 14:18:17 +05:30
sean-brydonandOmar López 9fffaa20a2 Fix providerName (#2589)
Co-authored-by: Omar López <zomars@me.com>
2022-04-26 04:20:13 +00:00
sean-brydonandkodiakhq[bot] fd73a4ac92 Fix Width (#2587)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-26 02:48:36 +00:00
sean-brydonandkodiakhq[bot] 29a6c70fc3 Heading Skeleton (#2601)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-26 02:38:41 +00:00
96f6c644bd Improve create event-type flow (#2446)
* Improve create event-type flow

* Update form values when query changes

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
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>
2022-04-26 02:13:15 +00:00
sean-brydonandPeer Richelsen 7c12bb1e20 Bug/i118n flicker (#2609)
* Fix Global Flicker

* Fixes Flicker + Null return

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-04-25 18:26:13 -06:00
Peer Richelsen 10e796f956 Revert "feat: disable select when loading (#2475)" (#2608)
This reverts commit 307b098f83.
2022-04-26 01:26:03 +02:00
sean-brydon 071077f2dc Fix Global Flicker (#2604) 2022-04-26 00:20:16 +02:00
sean-brydon afe957674c Adding avatar skeleton loader (#2600) 2022-04-25 20:18:07 +00:00
Carlos Gabriel 307b098f83 feat: disable select when loading (#2475) 2022-04-25 20:30:15 +02:00
sean-brydonandPeer Richelsen 95a793dd5a Skeleton Loaders Implementation (#2596)
* Skeleton Loaders

* Remove Href

* Fix Height Jumping around

* Subtle Colors

* feedback by ciaran

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-04-25 17:01:51 +00:00
sean-brydon a0057911c1 Fix back button (#2592) 2022-04-25 10:32:01 +02:00
Hariom Balhara 93c75b5fef Embed Miscellaneous Improvements and Fixes (#2499) 2022-04-25 10:03:00 +05:30
Afzal Sayed 53d7e57142 Fix update event type authorization (#2588) 2022-04-24 15:02:04 -06:00
Hariom Balhara 2c4a891a89 Add debugging details (#2582) 2022-04-23 18:45:46 +00:00
Joe Au-Yeung 8e0c7759be Update README.md (#2578) 2022-04-22 21:43:59 +02:00
Peer Richelsen 41dc01ea3c Update next-i18next.config.js (#2568) 2022-04-22 20:40:40 +02:00
Syed Ali Shahbaz 9c985edb6b away user fix (#2576) 2022-04-22 16:03:01 +01:00
Bailey Pumfleet 69ef309cb5 Update website submodule 2022-04-21 22:31:02 +01:00
Omar López f10bf38292 Removed deprecated JWT secrets (#2574) 2022-04-21 15:10:23 -06:00
Agusti Fernandez 02f68b104b fix: adds title to public-api docs (#2573) 2022-04-21 20:46:37 +00:00
8bc5a75249 Feature: Verify login on signup with magic link. (#2122)
* manual migration to rename verificationtoken, maybe it could be dropped and create a new table instead if we're not using it, will consult @zomars

* feat: rename verificationRequest --> verificationToken in schema.prisma

* fix: rename verificationRequest -> verificationToken in the codebase

* feat: add default cookies for next-auth

* fix: moves @lib/serverConfig to @calcom/lib so it can be called by website too

* fix: make self-certificate work in dev env by not rejecting tls in serverConfig

* fix verificationTokenToken typo

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

* Adds domain: .cal.com if not dev env in cookies

* Adds default-cookies to apps/web, and nextauth_domain to turbo website build deps"a

* update NEXTAUTH_DOMAIN to NEXTAUTH_COOKIE_DOMAIN

* Updates website submodule

* Removes deprecated env vars

* Consolidates auth logic in one place

* Updates website module

* Signup fixes

* Build fixes

* Updates example

* Updates example

* Fixes

* Fix Email Verification

* fix: move csrf-token cookiePrefix from __Host -> __Secure

* Removes console log

* Fixes link in email template

* Removed irrelevant coment

* Testing with a 32 bit secret

* Fixes for cookien in E2E

* E2E fixes

* Fixes Stripe tests locally

* Temp fix for E2E

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2022-04-21 14:32:25 -06:00
97e4cca252 Remove global app installed button (#2561)
* Remove global app installed button

* Add Jitsi add button

* Find app credentials based on variant

* Make huddle installable

* Remove default installed message from installed apps page

* Display Jitsi and Huddle as locations if installed

* Reverse global app changes and made Jitsi and Huddle non global

* Changes to app page refrence #2556

* Fix type errors

* Revert code

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-21 12:17:56 -06:00
sean-brydonandPeer Richelsen 18d41b52a2 Add chat public scope to slack scopes (#2564)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-04-21 19:04:38 +02:00
manuelguandPeer Richelsen 26c0f82edf fix: typo in slack app description (#2563)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-04-21 17:25:43 +02:00
SiderealArtandPeer Richelsen c12436afb0 Update Traditional Chinese translation (#2566)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-04-21 17:20:56 +02:00
milospuac fead885aa4 Create slack.mdx (#2565)
Documentation for our Slack commands
2022-04-21 14:26:29 +01:00
e680bb1548 Adding Scope for bot permissions (#2530)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-04-21 10:32:56 +01:00
alannncandOmar López 6e82d38249 Fix for appstore and wipemycal render (#2556)
* Fix for appstores and wipe-my-cal

* Fix email subject for reschedule

* Fix email subject for reschedule

* Fix api add wipemycal return

* Now we ask on a endpoint if app its installed

* Fix types

Co-authored-by: Omar López <zomars@me.com>
2022-04-20 23:37:25 +02:00
zomars 9f63299a1a Fixes hubspot metadata 2022-04-20 14:20:02 -06:00
zomars 702f31c935 Prepares prettier for local modules 2022-04-20 12:37:03 -06:00
zomars 08db282a07 Extracts EmptyScreen 2022-04-20 12:00:49 -06:00
zomars 080a394bb3 Renames package
# Conflicts:
#	package.json
2022-04-20 11:52:07 -06:00
zomars 8fb429e073 Renames package
# Conflicts:
#	package.json
2022-04-20 11:51:26 -06:00
Afzal Sayed 00a3ff89e4 Uniquely index event-type with teamId and slug (#2452) 2022-04-19 18:35:12 -06:00
Omar López 8f3b854559 Admin console (#2555)
* Adds admin submodule

* Adds admin submodule
2022-04-19 16:58:46 -06:00
Omar López 05edb144b2 Adds admin submodule (#2554) 2022-04-19 16:55:41 -06:00
8c173c840b fix(web): overflow with auto scroll instead of scroll (#2535)
* fix(readme): update setup steps

* fix(readme): update required node version in prerequisites section

* fix(web): overflow-auto instead of overflow-scroll

* Revert "fix(readme): update setup steps"

This reverts commit 2938d510c874e9298b82465baeae5af9181c01d8.

* Revert "fix(readme): update required node version in prerequisites section"

This reverts commit 848e1a153538475acb42daa6ddbd74dd474611f5.

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
2022-04-19 16:50:19 +01:00
Bailey Pumfleet b540f44d6c Create CODE_OF_CONDUCT.md 2022-04-19 14:27:44 +01:00
Agusti Fernandez 7493093a1a fix: dangling zero in api keys list (#2548) 2022-04-19 12:32:17 +01:00
Agusti Fernandez cf68541520 update swagger (#2547) 2022-04-19 10:50:40 +02:00
Peer Richelsen b4ee4413cc Update eventTypes.tsx (#2545) 2022-04-18 22:40:56 +02:00
Peer Richelsen f214830d0f Update README.mdx (#2544) 2022-04-18 22:08:49 +02:00
Peer Richelsen c92070a5a2 Update package.json 2022-04-18 19:53:49 +02:00
Hariom Balhara 102ca5403d Fix "Edit Booking" Icon (#2536) 2022-04-18 11:25:56 +01:00
Hariom Balhara 7fd57b88dc Fix mobile reschedule actions (#2534) 2022-04-17 17:37:16 +02:00
Hariom Balhara 5f57694148 Fix Mobile UI for Booking Availability Page. Make it consistent with booking page (#2533) 2022-04-17 13:25:11 +02:00
Peer Richelsen 73c97e85d4 overflow scroll for categories (#2502) 2022-04-16 20:37:19 +00:00
Syed Ali Shahbazandkodiakhq[bot] ccde0c20ab fixed avatargroup in mobile screens (#2498)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-16 22:23:15 +02:00
sean-brydon d2d3c67144 Display most recent (#2529) 2022-04-16 13:51:44 +02:00
Agusti Fernandez 6d5af81f68 adds authorize to swagger docs to pass apiKey (#2524) 2022-04-16 08:22:22 +01:00
Hariom Balhara 2e9d4125ed Fix Embed Documentation (#2525) 2022-04-16 06:17:47 +00:00
Omar López 56c32beebc Update README.md 2022-04-15 21:02:35 -06:00
faa67e0bb6 Feature: Adds api keys to cal.com webapp (#2277)
* feat: add ApiKey model for new Api auth, owned by a user

* fix: remove metadata:Json and add note:String instead in new apiKey model

* fix: rename apiKey to apiKeys in moder User relation in schema.prisma

* feat: add hashedKey to apiKey and lastUsedAt datetime to keep track of usage of keys and makiung them securely stored in db

* fix 30 day -> 30 days in expiresAt

* feat: api keys frontend in security page

* adds hashedKey to api key model, add frontend api keys in security page

* Make frontend work to create api keys with or without expiry, note, defaults to 1 month expiry

* remove migration for now, add env.example to swagger, sync api

* feat: hashed api keys

* fix: minor refactor and cleanup in apiKeys generator

* add api key success modal

* sync apps/api

* feat: We have API Keys in Security =)

* remove swagger env from pr

* apps api sync

* remove comments in password section

* feat: migration for api keys schema

* sync api w main

* delete apps/api

* add back apps/api

* make min date and disabled optional props in datepicker

* feat fix type check errors

* fix : types

* fix: rmeove renaming of verificationrequest token indexes in migration

* fix: remove extra div

* Fixes for feedback in PR

* fix button />

* fix: rename weird naming of translation for you_will_only_view_it_once

* fix: remove ternary and use && to avoid null for false

* fix sync apps/api with main not old commit

* fix empty className

* fix: remove unused imports

* fix remove commented jsx fragment close

* fix rename editing

* improve translations

* feat: adds beta tag in security tab under api keys

* fix: use api keys everywhere

* fix: cleanup code in api keys

* fix: use watch and controller for neverexpires/datepicker

* Fixes: improve api key never expires

* add back change password h2 title section in security page

* fix update env API_KEY_ prefix default to cal_

* fix: improve eidt api keys modal

* fix: update edit mutation in viewer.apiKeys

* Update apps/web/ee/components/apiKeys/ApiKeyListItem.tsx

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

* fix: item: any to pass build

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-04-15 20:58:34 -06:00
ffebe8e901 HubSpot App (#2380)
* Initial changes

* OAuth done and credentials stored

* Added "other" integrations

* Switching to hubspot api client

* Event creation for all attendees

* Update and delete done

* Doc update

* Fixing types

* App label is not mandatory

* Fixing bad merge: App label deleted

* Fixing bad automerge

* Removing  c.log

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-15 23:23:38 -03:00
Omar López 2cafe2d98e Update README.md 2022-04-15 20:12:24 -06:00
d03038d976 Fix: styling (#2523)
* remove vercel favicon and logo, remove empty space on top due to Head

* adds a negative margin-top to compensate for svg-assets

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-04-16 01:58:07 +00:00
Agusti FernandezandAgusti Fernandez Pardo 7e392da78a remove vercel favicon and logo, remove empty space on top due to Head (#2522)
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-04-16 03:35:49 +02:00
Agusti FernandezandAgusti Fernandez Pardo f8f3456b92 Adds iframe to developer.cal.com (swagger-ui) into docs/public-api.mdx (#2518)
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-04-15 23:56:32 +00:00
Agusti FernandezandAgusti Fernandez Pardo 3b637eefaa adds redirect for /api to developer.cal.com (#2517)
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-04-15 14:19:52 -06:00
46e1d28881 improve styling on mobile for swagger docs (#2516)
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-04-15 12:52:51 -06:00
Agusti Fernandez f23cc8b99f Adds env example and changes route (#2515) 2022-04-15 19:11:35 +01:00
Hariom Balhara 6843347dd7 Embed: Bump Versions and update Readmes and descriptions (#2507) 2022-04-15 18:55:36 +01:00
703 changed files with 27390 additions and 28996 deletions
+89
View File
@@ -0,0 +1,89 @@
# ********** INDEX **********
#
# - APP STORE
# - DAILY.CO VIDEO
# - GOOGLE CALENDAR/MEET/LOGIN
# - HUBSPOT
# - OFFICE 365
# - SLACK
# - STRIPE
# - TANDEM
# - ZOOM
# - GIPHY
# - VITAL
# - APP STORE **********************************************************************************************
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
# - DAILY.CO VIDEO
DAILY_API_KEY=
DAILY_SCALE_PLAN=''
# - GOOGLE CALENDAR/MEET/LOGIN
# Needed to enable Google Calendar integration and Login with Google
# @see https://github.com/calendso/calendso#obtaining-the-google-api-credentials
GOOGLE_API_CREDENTIALS='{}'
# To enable Login with Google you need to:
# 1. Set `GOOGLE_API_CREDENTIALS` above
# 2. Set `GOOGLE_LOGIN_ENABLED` to `true`
# When self-hosting please ensure you configure the Google integration as an Internal app so no one else can login to your instance
# @see https://support.google.com/cloud/answer/6158849#public-and-internal&zippy=%2Cpublic-and-internal-applications
GOOGLE_LOGIN_ENABLED=false
# - HUBSPOT
# Used for the HubSpot integration
# @see https://github.com/calcom/cal.com/#obtaining-hubspot-client-id-and-secret
HUBSPOT_CLIENT_ID=""
HUBSPOT_CLIENT_SECRET=""
# - OFFICE 365
# Used for the Office 365 / Outlook.com Calendar / MS Teams integration
# @see https://github.com/calcom/cal.com/#Obtaining-Microsoft-Graph-Client-ID-and-Secret
MS_GRAPH_CLIENT_ID=
MS_GRAPH_CLIENT_SECRET=
# - SLACK
# @see https://github.com/calcom/cal.com/#obtaining-slack-client-id-and-secret-and-signing-secret
SLACK_SIGNING_SECRET=
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
# - STRIPE
NEXT_PUBLIC_STRIPE_PUBLIC_KEY= # pk_test_...
STRIPE_PRIVATE_KEY= # sk_test_...
STRIPE_WEBHOOK_SECRET= # whsec_...
STRIPE_CLIENT_ID= # ca_...
PAYMENT_FEE_FIXED=10 # Take 10 additional cents commission
PAYMENT_FEE_PERCENTAGE=0.005 # Take 0.5% commission
# - TANDEM
# Used for the Tandem integration -- contact support@tandem.chat for API access.
TANDEM_CLIENT_ID=""
TANDEM_CLIENT_SECRET=""
TANDEM_BASE_URL="https://tandem.chat"
# - ZOOM
# Used for the Zoom integration
# @see https://github.com/calcom/cal.com/#obtaining-zoom-client-id-and-secret
ZOOM_CLIENT_ID=
ZOOM_CLIENT_SECRET=
# - GIPHY
# Used for the Giphy integration
# @see https://support.giphy.com/hc/en-us/articles/360020283431-Request-A-GIPHY-API-Key
GIPHY_API_KEY=
# - VITAL
# Used for the vital integration
# @see https://github.com/calcom/cal.com/#obtaining-vital-api-keys
VITAL_API_KEY=
VITAL_WEBHOOK_SECRET=
# "sandbox" | "prod" | "production" | "development"
VITAL_DEVELOPMENT_MODE="sandbox"
# "us" | "eu"
VITAL_REGION="us"
# - ZAPIER
# Used for the Zapier integration
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/zapier/README.md
ZAPIER_INVITE_LINK=""
# *********************************************************************************************************
+16 -59
View File
@@ -5,14 +5,6 @@
# - SHARED
# - NEXTAUTH
# - E-MAIL SETTINGS
# - APP STORE
# - DAILY.CO VIDEO
# - GOOGLE CALENDAR/MEET/LOGIN
# - OFFICE 365
# - SLACK
# - STRIPE
# - TANDEM
# - ZOOM
# - LICENSE *************************************************************************************************
# Set this value to 'agree' to accept our license:
@@ -23,6 +15,8 @@
# - You can not repackage or sell the codebase
# - Acquire a commercial license to remove these terms by visiting: cal.com/sales
NEXT_PUBLIC_LICENSE_CONSENT=''
# To enable enterprise-only features, fill your license key in here
CALCOM_LICENSE_KEY=
# ***********************************************************************************************************
# - DATABASE ************************************************************************************************
@@ -33,6 +27,8 @@ NEXT_PUBLIC_LICENSE_CONSENT=''
NEXT_PUBLIC_WEBAPP_URL='http://localhost:3000'
# Change to 'http://localhost:3001' if running the website simultaneously
NEXT_PUBLIC_WEBSITE_URL='http://localhost:3000'
NEXT_PUBLIC_CONSOLE_URL='http://localhost:3004'
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
# To enable SAML login, set both these variables
# @see https://github.com/calcom/cal.com/tree/main/packages/ee#setting-up-saml-login
@@ -47,10 +43,13 @@ PGSSLMODE=
# - NEXTAUTH
# @see: https://github.com/calendso/calendso/issues/263
# Required for Vercel hosting - set NEXTAUTH_URL to equal your BASE_URL
# @see: https://next-auth.js.org/configuration/options#nextauth_url
# Required for Vercel hosting - set NEXTAUTH_URL to equal your NEXT_PUBLIC_WEBAPP_URL
# NEXTAUTH_URL='http://localhost:3000'
NEXTAUTH_URL=
JWT_SECRET='secret'
# @see: https://next-auth.js.org/configuration/options#nextauth_secret
# You can use: `openssl rand -base64 32` to generate one
NEXTAUTH_SECRET=
# Used for cross-domain cookie authentication
NEXTAUTH_COOKIE_DOMAIN=.example.com
@@ -74,6 +73,10 @@ NEXT_PUBLIC_ZENDESK_KEY=
# Help Scout Config
NEXT_PUBLIC_HELPSCOUT_KEY=
# Inbox to send user feedback
SEND_FEEDBACK_EMAIL=
# This is used so we can bypass emails in auth flows for E2E testing
# Set it to "1" if you need to run E2E tests locally
NEXT_PUBLIC_IS_E2E=
@@ -83,6 +86,9 @@ NEXT_PUBLIC_STRIPE_PRO_PLAN_PRODUCT=
NEXT_PUBLIC_STRIPE_PRO_PLAN_PRICE=
NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE=
NEXT_PUBLIC_STRIPE_FREE_PLAN_PRICE=
# Use for internal Public API Keys and optional
API_KEY_PREFIX=cal_
# ***********************************************************************************************************
# - E-MAIL SETTINGS *****************************************************************************************
@@ -107,52 +113,3 @@ EMAIL_SERVER_PASSWORD='<office365_password>'
## @see https://support.google.com/accounts/answer/185833
# EMAIL_SERVER_PASSWORD='<gmail_app_password>'
# **********************************************************************************************************
# - APP STORE **********************************************************************************************
# - DAILY.CO VIDEO
DAILY_API_KEY=
DAILY_SCALE_PLAN=''
# - GOOGLE CALENDAR/MEET/LOGIN
# Needed to enable Google Calendar integration and Login with Google
# @see https://github.com/calendso/calendso#obtaining-the-google-api-credentials
GOOGLE_API_CREDENTIALS='{}'
# To enable Login with Google you need to:
# 1. Set `GOOGLE_API_CREDENTIALS` above
# 2. Set `GOOGLE_LOGIN_ENABLED` to `true`
# When self-hosting please ensure you configure the Google integration as an Internal app so no one else can login to your instance
# @see https://support.google.com/cloud/answer/6158849#public-and-internal&zippy=%2Cpublic-and-internal-applications
GOOGLE_LOGIN_ENABLED=false
# - OFFICE 365
# Used for the Office 365 / Outlook.com Calendar / MS Teams integration
# @see https://github.com/calcom/cal.com/#Obtaining-Microsoft-Graph-Client-ID-and-Secret
MS_GRAPH_CLIENT_ID=
MS_GRAPH_CLIENT_SECRET=
# - SLACK
# @see https://github.com/calcom/cal.com/#obtaining-slack-client-id-and-secret-and-signing-secret
SLACK_SIGNING_SECRET=
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
# - STRIPE
NEXT_PUBLIC_STRIPE_PUBLIC_KEY= # pk_test_...
STRIPE_PRIVATE_KEY= # sk_test_...
STRIPE_WEBHOOK_SECRET= # whsec_...
STRIPE_CLIENT_ID= # ca_...
PAYMENT_FEE_FIXED=10 # Take 10 additional cents commission
PAYMENT_FEE_PERCENTAGE=0.005 # Take 0.5% commission
# - TANDEM
# Used for the Tandem integration -- contact support@tandem.chat to for API access.
TANDEM_CLIENT_ID=""
TANDEM_CLIENT_SECRET=""
TANDEM_BASE_URL="https://tandem.chat"
# - ZOOM
# Used for the Zoom integration
# @see https://github.com/calcom/cal.com/#obtaining-zoom-client-id-and-secret
ZOOM_CLIENT_ID=
ZOOM_CLIENT_SECRET=
# *********************************************************************************************************
+6
View File
@@ -4,6 +4,12 @@
Fixes # (issue)
<!-- Please provide a loom video for visual changes to speed up reviews
Loom Video: https://www.loom.com/
-->
**Environment**: Staging(main branch) / Production
## Type of change
<!-- Please delete options that are not relevant. -->
+6 -4
View File
@@ -1,6 +1,6 @@
name: Check types
on:
pull_request:
pull_request_target:
branches:
- main
jobs:
@@ -16,11 +16,13 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v2
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 2
- 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
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v2
- name: crowdin action
uses: crowdin/github-action@1.4.2
uses: crowdin/github-action@1.4.9
with:
upload_translations: true
download_translations: true
+110
View File
@@ -0,0 +1,110 @@
name: E2E test - embed
on:
push:
branches: [ tests/ci-embed ]
pull_request_target: # So we can test on forks
branches:
- main
# Embed e2e - tests verify booking flow which is applicable to non-embed case also. So, don't ignore apps/web changes.
paths-ignore:
- apps/api/**
- apps/console/**
- apps/docs/**
- apps/swagger/**
- apps/website/**
- apps/web/public/**
- tests/**
- playwright/**
jobs:
test:
timeout-minutes: 20
name: Testing Embeds
strategy:
matrix:
node: ["14.x"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
env:
DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
NEXT_PUBLIC_WEBAPP_URL: http://localhost:3000
NEXT_PUBLIC_WEBSITE_URL: http://localhost:3000
NEXTAUTH_SECRET: secret
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: true
# CRON_API_KEY: xxx
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
PAYMENT_FEE_PERCENTAGE: 0.005
PAYMENT_FEE_FIXED: 10
SAML_DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
SAML_ADMINS: pro@example.com
NEXTAUTH_URL: http://localhost:3000/api/auth
NEXT_PUBLIC_IS_E2E: 1
# EMAIL_FROM: e2e@cal.com
# EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
# EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
# EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
# EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD }}
# MS_GRAPH_CLIENT_ID: xxx
# 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
env:
POSTGRES_USER: postgres
POSTGRES_DB: calendso
ports:
- 5432:5432
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
fetch-depth: 2
- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: "yarn"
- name: Cache playwright binaries
uses: actions/cache@v2
id: playwright-cache
with:
path: |
~/Library/Caches/ms-playwright
~/.cache/ms-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 embed-tests-prepare
- run: yarn workspace @calcom/embed-core embed-tests-update-snapshots:ci
- run: yarn workspace @calcom/embed-react embed-tests-update-snapshots:ci
- name: Upload embed-core results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results-core
path: packages/embeds/embed-core/playwright/results
- name: Upload embed-react results
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: test-results-react
path: packages/embeds/embed-react/playwright/results
+13 -25
View File
@@ -4,10 +4,10 @@ on:
branches:
- main
paths-ignore:
- public/static/locales/**
- apps/web/public/static/locales/**
jobs:
test:
timeout-minutes: 15
timeout-minutes: 20
name: Testing ${{ matrix.node }} and ${{ matrix.os }}
strategy:
matrix:
@@ -19,7 +19,7 @@ jobs:
DATABASE_URL: postgresql://postgres:@localhost:5432/calendso
NEXT_PUBLIC_WEBAPP_URL: http://localhost:3000
NEXT_PUBLIC_WEBSITE_URL: http://localhost:3000
JWT_SECRET: secret
NEXTAUTH_SECRET: secret
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
GOOGLE_LOGIN_ENABLED: true
# CRON_API_KEY: xxx
@@ -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
+4 -5
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
+1
View File
@@ -39,6 +39,7 @@ yarn-error.log*
.env.production.local
.env.*
!.env.example
!.env.appStore.example
# vercel
.vercel
+4
View File
@@ -1,3 +1,7 @@
[submodule "apps/console"]
path = apps/console
url = https://github.com/calcom/console.git
branch = main
[submodule "apps/api"]
path = apps/api
url = https://github.com/calcom/api.git
+78
View File
@@ -0,0 +1,78 @@
{
"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)",
"Maildev(587)"
],
// 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": []
},
{
"label": "Maildev(587)",
"type": "shell",
"command": "maildev -s 587",
"isBackground": false,
"problemMatcher": []
}
]
}
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
bailey@cal.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+20
View File
@@ -4,6 +4,26 @@ Contributions are what make the open source community such an amazing place to b
- Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission.
## Areas of expertise
### Legend
✅ = has knowledge
🥇 = is their main priority
⚠️ = is the only one with knowledge
👀 = has no knowledge but wants to be onboarded
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://dynamic-svgs.vercel.app/image.svg?dark">
<img alt="Areas of expertise table" src="https://dynamic-svgs.vercel.app/image.svg">
</picture>
## Developing
The development branch is `main`. This is the branch that all pull
+62 -35
View File
@@ -18,22 +18,26 @@
<a href="https://cal.com">Website</a>
·
<a href="https://github.com/calcom/cal.com/issues">Issues</a>
·
<a href="https://cal.com/roadmap">Roadmap</a>
</p>
</p>
<p align="center">
<a href="https://cal.com/slack"><img src="https://img.shields.io/badge/Slack-calendso.slack.com-%234A154B" alt="Join Cal.com Slack"></a>
<a href="https://www.producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
<a href="https://news.ycombinator.com/item?id=26817795"><img src="https://img.shields.io/badge/Hacker%20News-311-%23FF6600" alt="Hacker News"></a>
<a href="https://github.com/calcom/cal.com/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License"></a>
<a href="https://github.com/calcom/cal.com/pulse"><img src="https://img.shields.io/github/commit-activity/m/calcom/cal.com" alt="Commits-per-month"></a>
<a href="https://cal.com/pricing"><img src="https://img.shields.io/badge/Pricing-%2412%2Fmonth-brightgreen" alt="Pricing"></a>
<a href="https://jitsu.com?utm_source=github/calcom/cal.com"><img src="https://img.shields.io/badge/Metrics_tracked_by-JITSU-AA00FF?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACKSURBVHgBrZDRCYAwDEQv6gCOoKO4hOCXI9QVnEZwiY5iF5GaVClaBNtioCSUvCR3tMJaxIfZgW4AGUoEPVwgPZoS0Dmgg3NBVDFNbMIsmYCak3J1jDk9iCQvsKJvkzr71N81Gj6vDT/LU2P6RhY63jcafk3YJEbgeZpiFyc/5HJKv8Ef273NSfABGbQfUZhnOSAAAAAASUVORK5CYII=" alt="Jitsu Tracked"></a>
<a href="https://hub.docker.com/r/calendso/calendso"><img src="https://img.shields.io/docker/pulls/calendso/calendso"></a>
<a href="https://twitter.com/calcom"><img src="https://img.shields.io/twitter/follow/calcom?style=social"></a>
<a href="https://calendso.slack.com/archives/C02BY67GMMW"><img src="https://img.shields.io/badge/translations-contribute-brightgreen" /></a>
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
<a href="https://news.ycombinator.com/item?id=26817795"><img src="https://img.shields.io/badge/Hacker%20News-311-%23FF6600" alt="Hacker News"></a>
<a href="https://github.com/calcom/cal.com/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License"></a>
<a href="https://github.com/calcom/cal.com/pulse"><img src="https://img.shields.io/github/commit-activity/m/calcom/cal.com" alt="Commits-per-month"></a>
<a href="https://cal.com/pricing"><img src="https://img.shields.io/badge/Pricing-%2412%2Fmonth-brightgreen" alt="Pricing"></a>
<a href="https://jitsu.com?utm_source=github/calcom/cal.com"><img src="https://img.shields.io/badge/Metrics_tracked_by-JITSU-AA00FF?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACKSURBVHgBrZDRCYAwDEQv6gCOoKO4hOCXI9QVnEZwiY5iF5GaVClaBNtioCSUvCR3tMJaxIfZgW4AGUoEPVwgPZoS0Dmgg3NBVDFNbMIsmYCak3J1jDk9iCQvsKJvkzr71N81Gj6vDT/LU2P6RhY63jcafk3YJEbgeZpiFyc/5HJKv8Ef273NSfABGbQfUZhnOSAAAAAASUVORK5CYII=" alt="Jitsu Tracked"></a>
<img src="https://api.checklyhq.com/v1/badges/checks/5e048048-1b51-47ba-9209-60607507622e?responseTime=true" alt="Checkly Availability" />
<a href="https://hub.docker.com/r/calendso/calendso"><img src="https://img.shields.io/docker/pulls/calendso/calendso"></a>
<a href="https://twitter.com/calcom"><img src="https://img.shields.io/twitter/follow/calcom?style=flat"></a>
<a href="https://twitch.tv/calcomtv"><img src="https://img.shields.io/twitch/status/calcomtv?style=flat"></a>
<a href="https://calendso.slack.com/archives/C02BY67GMMW"><img src="https://img.shields.io/badge/translations-contribute-brightgreen" /></a>
<a href="https://www.contributor-covenant.org/version/1/4/code-of-conduct/ "><img src="https://img.shields.io/badge/Contributor%20Covenant-1.4-purple" /></a>
</p>
<!-- ABOUT THE PROJECT -->
@@ -102,19 +106,14 @@ Here is what you need to be able to run Cal.
cd cal.com
```
1. Copy `apps/web/.env.example` to `apps/web/.env`
```sh
cp apps/web/.env.example apps/web/.env
cp packages/prisma/.env.example packages/prisma/.env
```
1. Install packages with yarn
```sh
yarn
```
1. Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the .env file.
#### Quick start with `yarn dx`
> - **Requires Docker and Docker Compose to be installed**
@@ -126,10 +125,10 @@ yarn dx
#### Development tip
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `apps/web/.env` to get logging information for all the queries and mutations driven by **trpc**.
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `.env` to get logging information for all the queries and mutations driven by **trpc**.
```sh
echo 'NEXT_PUBLIC_DEBUG=1' >> apps/web/.env
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
```
#### Manual setup
@@ -195,11 +194,11 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> apps/web/.env
### E2E-Testing
Be sure to set the environment variable `NEXTAUTH_URL` to the correct value. If you are running locally, as the documentation within `.env.example` mentions, the value should be `http://localhost:3000`.
```sh
# In first terminal. Must run on port 3000.
yarn dx
# In second terminal
yarn workspace @calcom/web test-e2e
# In a terminal just run:
yarn test-e2e
# To open last HTML report run:
yarn workspace @calcom/web playwright-report
@@ -213,7 +212,13 @@ yarn workspace @calcom/web playwright-report
git pull
```
2. Apply database migrations by running <b>one of</b> the following commands:
1. Check if dependencies got added/updated/removed
```sh
yarn
```
1. Apply database migrations by running <b>one of</b> the following commands:
In a development environment, run:
@@ -229,16 +234,13 @@ yarn workspace @calcom/web playwright-report
yarn workspace @calcom/prisma db-deploy
```
3. Check the `.env.example` and compare it to your current `.env` file. In case there are any fields not present
in your current `.env`, add them there.
1. Check for `.env` variables changes
For the current version, especially check if the variable `BASE_URL` is present and properly set in your environment, for example:
```
BASE_URL='https://yourdomain.com'
```sh
yarn predev
```
4. Start the server. In a development environment, just do:
1. Start the server. In a development environment, just do:
```sh
yarn dev
@@ -251,7 +253,7 @@ yarn workspace @calcom/web playwright-report
yarn start
```
5. Enjoy the new version.
1. Enjoy the new version.
<!-- DEPLOYMENT -->
## Deployment
@@ -349,6 +351,7 @@ oauth_config:
bot:
- chat:write
- commands
- chat:write.public
settings:
interactivity:
is_enabled: true
@@ -391,6 +394,30 @@ Next make sure you have your app running `yarn dx`. Then in the slack chat type
4. Now paste the API key to your .env file into the `DAILY_API_KEY` field in your .env file.
5. If you have the [Daily Scale Plan](https://www.daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
### Obtaining HubSpot Client ID and Secret
1. Open [HubSpot Developer](https://developer.hubspot.com/) and sign into your account, or create a new one.
2. From within the home of the Developer account page, go to "Manage apps".
3. Click "Create app" button top right.
4. Fill in any information you want in the "App info" tab
5. Go to tab "Auth"
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot othercalendar/callback` replacing Cal.com URL with the URI at which your application runs.
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
9. Click the "Save" button at the bottom footer.
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
### Obtaining Vital API Keys
1. Open [Vital](https://tryvital.io/) and click Get API Keys.
1. Create a team with the team name you desire
1. Head to the configuration section on the sidebar of the dashboard
1. Click on API keys and you'll find your sandbox `api_key`.
1. Copy your `api_key` to `VITAL_API_KEY` in the .env.appStore file.
1. Open [Vital Webhooks](https://app.tryvital.io/team/{team_id}/webhooks) and add `<CALCOM BASE URL>/api/integrations/vital/webhook` as webhook for connected applications.
1. Select all events for the webhook you interested, e.g. `sleep_created`
1. Copy the webhook secret (`sec...`) to `VITAL_WEBHOOK_SECRET` in the .env.appStore file.
<!-- LICENSE -->
## License
@@ -410,7 +437,7 @@ Special thanks to these amazing projects which help power Cal.com:
- [Day.js](https://day.js.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [Prisma](https://prisma.io/)
<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>
[<img src="https://jitsu.com/img/powered-by-jitsu.png?gh=true">](https://jitsu.com/?utm_source=cal.com-gihub)
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 -1
View File
@@ -18,7 +18,7 @@
"description": "Application Key for symmetric encryption and decryption. Must be 32 bytes for AES256 encryption algorithm.",
"value": "secret"
},
"JWT_SECRET": "secret"
"NEXTAUTH_SECRET": "secret"
},
"scripts": {
"postdeploy": "cd packages/prisma && npx prisma migrate deploy"
Submodule
+1
Submodule apps/console added at b6b26f4792
+1 -1
View File
@@ -5,7 +5,7 @@
</a>
<a href="https://cal.com">Website</a>
·
<a href="https://github.com/calcom/docs/issues">Community Support</a>
<a href="https://github.com/calcom/cal.com/issues">Community Support</a>
</div>
# Cal.com Documentation
+21
View File
@@ -0,0 +1,21 @@
function getAnchor(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9 ]/g, "")
.replace(/[ ]/g, "-")
.replace(/ /g, "%20");
}
export default function Anchor({ as, children }) {
const anchor = getAnchor(children);
const link = `#${anchor}`;
const Component = as || "div";
return (
<Component id={anchor}>
<a href={link} className="anchor-link">
§
</a>
{children}
</Component>
);
}
+33
View File
@@ -0,0 +1,33 @@
import { useState, useEffect } from "react";
// Define general type for useWindowSize hook, which includes width and height
export interface Size {
width: number | undefined;
height: number | undefined;
}
// Hook from: https://usehooks.com/useWindowSize/
export function useWindowSize(): Size {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState<Size>({
width: undefined,
height: undefined,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}
+12 -1
View File
@@ -1,6 +1,17 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const withNextra = require("nextra")({
theme: "nextra-theme-docs",
themeConfig: "./theme.config.js",
unstable_staticImage: true,
});
module.exports = withNextra();
module.exports = withNextra({
async rewrites() {
return [
// This redirects requests recieved at /api to /public-api to workaround nextjs default use of /api.
{
source: "/api",
destination: "/public-api",
},
];
},
});
+5 -4
View File
@@ -15,14 +15,15 @@
"author": "Cal.com, Inc.",
"license": "MIT",
"dependencies": {
"next": "^12.1.0",
"iframe-resizer-react": "^1.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"
}
}
+1 -1
View File
@@ -12,6 +12,6 @@ This is also the home of our design system documentation and developer docs.
If you don't already know what Cal.com is about, please head over to [our website](https://cal.com), where you can learn more about the product before venturing into the documentation.
Want to help make these docs even better? This site is fully open source, and the source code is available on [GitHub](https://github.com/calcom/docs). You can also click the edit button at the bottom of any page to start editing the source code and start a pull request.
Want to help make these docs even better? This site is fully open source, and the source code is available on [GitHub](https://github.com/calcom/cal.com/tree/main/apps/docs). You can also click the edit button at the bottom of any page to start editing the source code and start a pull request.
<Bleed>![Telemedicine Example](https://cal.com/telemedicine-example.svg)</Bleed>
+90 -36
View File
@@ -2,17 +2,18 @@
title: Embed
---
import Anchor from "../../components/Anchor"
# Embed
The Embed allows your website visitors to book a meeting with you directly from your website.
## Install on any website
TODO: Mention possibility of installation through tag managers as well
Install the following Vanilla JS Snippet to get embed to work on any website. After that you can <a href="#popular-ways-in-which-you-can-embed-on-your-website">choose any of the ways</a> to show your Cal Link embedded on your website.
- _Step-1._ Install the Vanilla JS Snippet
```javascript
```html
<script>
(function (C, A, L) {
let p = function (a, ar) {
a.q.push(ar);
@@ -40,14 +41,10 @@ TODO: Mention possibility of installation through tag managers as well
}
p(cal, ar);
};
})(window, "https://cal.com/embed.js", "init");
```
- _Step-2_. Initialize it
```javascript
Cal("init)
```
})(window, "https://cal.com/embed.js", "init");
Cal("init")
</script>
```
## Install with a Framework
@@ -63,9 +60,9 @@ yarn add @calcom/embed-react
You can use Vanilla JS Snippet to install
## Popular ways in which you can embed on your website
<Anchor as="H2">Popular ways in which you can embed on your website</Anchor>
Assuming that you have followed the steps of installing and initializing the snippet, you can add show the embed in following ways:
Assuming that you have followed the steps of installing and initializing the snippet, you can show the embed in following ways:
### Inline
@@ -74,20 +71,29 @@ Show the embed inline inside a container element. It would take the width and he
<details>
<summary>_Vanilla JS_</summary>
```javascript
Cal("inline", {
elementOrSelector: "Your Embed Container Selector Path", // You can also provide an element directly
calLink: "jane", // The link that you want to embed. It would open https://cal.com/jane in embed
config: {
name: "John Doe", // Prefill Name
email: "johndoe@gmail.com", // Prefill Email
notes: "Test Meeting", // Prefill Notes
guests: ["janedoe@gmail.com", "test@gmail.com"], // Prefill Guests
theme: "dark", // "dark" or "light" theme
},
});
```
```html
<script>
Cal("inline", {
elementOrSelector: "Your Embed Container Selector Path", // You can also provide an element directly
calLink: "jane", // The link that you want to embed. It would open https://cal.com/jane in embed
config: {
name: "John Doe", // Prefill Name
email: "johndoe@gmail.com", // Prefill Email
notes: "Test Meeting", // Prefill Notes
guests: ["janedoe@gmail.com", "test@gmail.com"], // Prefill Guests
theme: "dark", // "dark" or "light" theme
},
});
</script>
*Sample sandbox*
```
<iframe src="https://codesandbox.io/embed/vanilla-js-inline-embed-r27n67?fontsize=14&hidenavigation=1&theme=dark"
style={{width:"100%", height:"500px", border:0, borderRadius: "4px", overflow:"hidden"}}
title="Cal Component - Embed Inline Demo[React][TypeScript]"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
</details>
####
@@ -112,6 +118,14 @@ const MyComponent = () => (
);
```
*Sample sandbox*
<iframe src="https://codesandbox.io/embed/cal-component-embed-inline-demo-react-typescript-d1zlcn?fontsize=14&hidenavigation=1&theme=dark"
style={{width:"100%", height:"500px", border:0, borderRadius: "4px", overflow:"hidden"}}
title="Cal Component - Embed Inline Demo[React][TypeScript]"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
</details>
### Popup on any existing element
@@ -124,9 +138,16 @@ To show the embed as a popup on clicking an element, add `data-cal-link` attribu
To show the embed as a popup on clicking an element, simply add `data-cal-link` attribute to the element.
*Sample sandbox*
<iframe src="https://codesandbox.io/embed/popup-on-click-of-an-existing-element-y9lcuo?fontsize=14&hidenavigation=1&theme=dark"
style={{width:"100%", height:"500px", border:0, borderRadius: "4px", overflow:"hidden"}}
title="Cal Component - Embed Inline Demo[React][TypeScript]"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
<button data-cal-link="jane" data-cal-config="A valid config JSON"></button>
</details>
<details>
<summary>React</summary>
```jsx
@@ -135,11 +156,36 @@ To show the embed as a popup on clicking an element, simply add `data-cal-link`
const MyComponent = ()=> {
return <button data-cal-link="jane" data-cal-config='A valid config JSON'></button>
}
```
````
*Sample sandbox*
<iframe src="https://codesandbox.io/embed/embed-popup-on-click-of-an-existing-element-demo-react-sc967e?fontsize=14&hidenavigation=1&theme=dark"
style={{width:"100%", height:"500px", border:0, borderRadius: "4px", overflow:"hidden"}}
title="Cal Component - Embed Inline Demo[React][TypeScript]"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
</details>
### Full Screen
### Floating pop-up button
```html
<script>
Cal("floatingButton", {
// The link that you want to embed. It would open https://cal.com/jane in embed
calLink: "jane",
});
</script>
```
*Sample sandbox*
<iframe src="https://codesandbox.io/embed/embed-floating-button-popup-all-websites-cg7pru?fontsize=14&hidenavigation=1&theme=dark"
style={{width:"100%", height:"500px", border:0, borderRadius: "4px", overflow:"hidden"}}
title="Cal Component - Embed Inline Demo[React][TypeScript]"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
## Supported Instructions
@@ -149,8 +195,10 @@ Consider an instruction as a function with that name and that would be called wi
Appends embed inline as the child of the element.
```javascript
```html
<script>
Cal("inline", { elementOrSelector, calLink });
</script>
````
- `elementOrSelector` - Give it either a valid CSS selector or an HTMLElement instance directly
@@ -161,8 +209,10 @@ Cal("inline", { elementOrSelector, calLink });
Configure UI for embed. Make it look part of your webpage.
```javascript
```html
<script>
Cal("inline", { styles });
</script>
```
- `styles` - It supports styling for `body` and `eventTypeListItem`. Right now we support just background on these two.
@@ -173,15 +223,18 @@ Usage:
If you want to open cal link on some action. Make it pop open instantly by preloading it.
```javascript
```html
<script>
Cal("preload", { calLink });
</script>
```
- `calLink` - Cal Link that you want to embed e.g. john. Just give the username. No need to give the full URL [https://cal.com/john]()
## Actions
You can listen to an action that occurs in embedded cal link as follows. You can think of them as DOM events. We are avoiding the term events to not confuse it with Cal Events.
```javascript
You can listen to an action that occurs in embedded cal link as follows. You can think of them as DOM events. We are avoiding the term "events" to not confuse it with Cal Events.
```html
<script>
Cal("on", {
action: "ANY_ACTION_NAME",
callback: (e)=>{
@@ -191,11 +244,12 @@ Cal("on", {
const {data, type, namespace} = e.detail;
}
})
</script>
```
Following are the list of supported actions.
-
| action | description | properties |
| Action | Description | Properties |
|----------------------|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| eventTypeSelected | When user chooses an event-type from the listing. | eventType:object // Event Type that has been selected" |
| bookingSuccessful | When the booking is successfully done. It might not be confirmed. | confirmed: boolean; //Whether confirmation from organizer is pending or not <br/><br/>eventType: "Object for Event Type that has been booked"; <br/><br/>date: string; // Date of Event <br/><br/>duration: number; //Duration of booked Event <br/><br/>organizer: object //Organizer details like name, timezone, email |
+21
View File
@@ -0,0 +1,21 @@
---
Title: Slack
---
# Slack
## Connecting
Connecting the bot is easy - If you are a workspace admin, the install button will add the bot to the workspace and also authorize your account with the bot. If you are a normal user, the install button will connect your Slack account with Cal.com. This will allow you to perform commands in Slack.
## Commands
`/today` - This command will display all meetings you have in your Cal.com profile for the current day. This will send a hidden message (not visible to anyone other than you) to the channel you issued the command in.
`/create-event` - It will display a modal allowing you to simply create a meeting invite with anyone in Slack. Success/Error information will be displayed in a private direct message from the bot.
`/links` - This command will post all your Cal.com meeting links into the current Slack channel you are in. **Note**: The bot needs to have permission to talk in the channel you are sending the message in. Otherwise, you won't be able to send your links.
As this is the beggining stage of our Slack integration, we plan on adding more commands in the future that will further improve your Cal.com experience.
## Self-Hosted
If you are using our self-hosted version, please refer to our documentation in
[cal.com/README.md](https://github.com/calcom/cal.com/blob/main/README.md#obtaining-slack-client-id-and-secret-and-signing-secret)
+1
View File
@@ -6,6 +6,7 @@
"event-types": "Event Types",
"teams": "Teams",
"integrations": "Integrations",
"public-api": "API",
"webhooks": "Webhooks",
"settings": "Settings",
"import": "Import",
+20
View File
@@ -0,0 +1,20 @@
import Bleed from 'nextra-theme-docs/bleed'
import Head from "next/head";
import IframeResizer from "iframe-resizer-react";
import {useWindowSize} from "../lib/useWindowSize";
<Bleed full>
<Head><title>Public API | Cal.com</title></Head>
<IframeResizer
autoResize
src={process.env.NEXT_PUBLIC_SWAGGER_DOCS_URL || "https://developer.cal.com"}
frameBorder="0"
style={{
width: useWindowSize().width > 768 ? "calc(100vw - 16rem)": "100vw",
minHeight: useWindowSize().width > 768 ? "100vh" : "200vh",
height: "auto",
border: 0,
}}
/>
</Bleed>
-3623
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
NEXT_PUBLIC_SWAGGER_DOCS_URL=http://localhost:3002/api/docs
+353
View File
@@ -0,0 +1,353 @@
import * as OpenAPISnippet from "openapi-snippet";
export const requestSnippets = {
generators: {
curl_bash: {
title: "cURL (bash)",
syntax: "bash",
},
curl_powershell: {
title: "cURL (PowerShell)",
syntax: "powershell",
},
curl_cmd: {
title: "cURL (CMD)",
syntax: "bash",
},
node: {
title: "Node",
syntax: "node",
},
},
defaultExpanded: true,
languages: ["node", "curl_bash"],
};
// Since swagger-ui-react was not configured to change the request snippets some workarounds required
// configuration will be added programatically
// Custom Plugin
export const SnippedGenerator = {
statePlugins: {
// extend some internals to gain information about current path, method and spec in the generator function metioned later
spec: {
wrapSelectors: {
requestFor: (ori, system) => (state, path, method) => {
return ori(path, method)
?.set("spec", state.get("json", {}))
?.setIn(["oasPathMethod", "path"], path)
?.setIn(["oasPathMethod", "method"], method);
},
mutatedRequestFor: (ori) => (state, path, method) => {
return ori(path, method)
?.set("spec", state.get("json", {}))
?.setIn(["oasPathMethod", "path"], path)
?.setIn(["oasPathMethod", "method"], method);
},
},
},
// extend the request snippets core plugin
requestSnippets: {
wrapSelectors: {
// add additional snippet generators here
getSnippetGenerators:
(ori, system) =>
(state, ...args) =>
ori(state, ...args)
// add node native snippet generator
// .set(
// // key
// "node_native",
// // config and generator function
// system.Im.fromJS({
// title: "NodeJs Native",
// syntax: "javascript",
// hostname: "test",
// fn: (req) => {
// // get extended info about request
// const { spec, oasPathMethod } = req.toJS();
// const { path, method } = oasPathMethod;
// // run OpenAPISnippet for target node
// const targets = ["node_native"];
// let snippet;
// try {
// // set request snippet content
// snippet = OpenAPISnippet.getEndpointSnippets(
// spec,
// path,
// method,
// targets
// // Since I don't know why hostname was undefinedundefined, I harcoded it here
// ).snippets[0].content;
// } catch (err) {
// // set to error in case it happens the npm package has some flaws
// snippet = JSON.stringify(snippet);
// }
// // return stringified snipped
// return snippet;
// },
// })
// )
.set(
// key
"node_fetch",
// config and generator function
system.Im.fromJS({
title: "NodeJS",
syntax: "javascript",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["node_fetch"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"shell_httpie",
// config and generator function
system.Im.fromJS({
title: "HTTPie",
syntax: "bash",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["shell_httpie"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"php_curl",
// config and generator function
system.Im.fromJS({
title: "PHP",
syntax: "php",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["php_curl"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"java_okhttp",
// config and generator function
system.Im.fromJS({
title: "Java",
syntax: "java",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
console.log(spec, oasPathMethod, path, method);
// run OpenAPISnippet for target node
const targets = ["java_okhttp"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
// .set(
// // key
// "java",
// // config and generator function
// system.Im.fromJS({
// title: "Java (Unirest)",
// syntax: "java",
// fn: (req) => {
// // get extended info about request
// const { spec, oasPathMethod } = req.toJS();
// const { path, method } = oasPathMethod;
// // run OpenAPISnippet for target node
// const targets = ["java"];
// let snippet;
// try {
// // set request snippet content
// snippet = OpenAPISnippet.getEndpointSnippets(
// spec,
// path,
// method,
// targets
// ).snippets[0].content;
// } catch (err) {
// // set to error in case it happens the npm package has some flaws
// snippet = JSON.stringify(snippet);
// }
// // return stringified snipped
// return snippet;
// },
// })
// )
// .set(
// // key
// "c_libcurl",
// // config and generator function
// system.Im.fromJS({
// title: "C (libcurl) ",
// syntax: "bash",
// fn: (req) => {
// // get extended info about request
// const { spec, oasPathMethod } = req.toJS();
// const { path, method } = oasPathMethod;
// // run OpenAPISnippet for target node
// const targets = ["c_libcurl"];
// let snippet;
// try {
// // set request snippet content
// snippet = OpenAPISnippet.getEndpointSnippets(
// spec,
// path,
// method,
// targets
// ).snippets[0].content;
// } catch (err) {
// // set to error in case it happens the npm package has some flaws
// snippet = JSON.stringify(snippet);
// }
// // return stringified snipped
// return snippet;
// },
// })
// )
.set(
// key
"go_native",
// config and generator function
system.Im.fromJS({
title: "Go",
syntax: "bash",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["go_native"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"ruby",
// config and generator function
system.Im.fromJS({
title: "Ruby",
syntax: "ruby",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["ruby"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
)
.set(
// key
"python",
// config and generator function
system.Im.fromJS({
title: "Python",
syntax: "python",
fn: (req) => {
// get extended info about request
const { spec, oasPathMethod } = req.toJS();
const { path, method } = oasPathMethod;
// run OpenAPISnippet for target node
const targets = ["python"];
let snippet;
try {
// set request snippet content
snippet = OpenAPISnippet.getEndpointSnippets(spec, path, method, targets).snippets[0]
.content;
} catch (err) {
// set to error in case it happens the npm package has some flaws
snippet = JSON.stringify(snippet);
}
// return stringified snipped
return snippet;
},
})
),
},
},
},
};
+12 -10
View File
@@ -3,21 +3,23 @@
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "PORT=4200 next dev",
"build": "next build",
"start": "next start"
"start": "PORT=4200 next start"
},
"dependencies": {
"highlight.js": "^11.5.1",
"isarray": "2.0.5",
"next": "12.1.4",
"react": "17.0.2",
"react-dom": "17.0.2",
"swagger-ui-react": "4.8.1"
"next": "12.1.6",
"openapi-snippet": "^0.13.0",
"react": "18.1.0",
"react-dom": "18.1.0",
"swagger-ui-react": "4.11.1"
},
"devDependencies": {
"@types/node": "16.11.26",
"@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"
}
}
+3 -1
View File
@@ -1,5 +1,7 @@
import "highlight.js/styles/default.css";
import "swagger-ui-react/swagger-ui.css";
import "../styles/globals.css";
import "../styles/swagger-cal.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
+21 -40
View File
@@ -1,45 +1,26 @@
import Head from "next/head";
import SwaggerUI from "swagger-ui-react";
import dynamic from "next/dynamic";
import { SwaggerUI } from "swagger-ui-react";
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
const SwaggerUIDynamic: SwaggerUI & { url: string } = dynamic(() => import("swagger-ui-react"), {
ssr: false,
});
const requestSnippets = {
generators: {
curl_bash: {
title: "cURL (bash)",
syntax: "bash",
},
curl_powershell: {
title: "cURL (PowerShell)",
syntax: "powershell",
},
curl_cmd: {
title: "cURL (CMD)",
syntax: "bash",
},
node: {
title: "Node",
syntax: "node",
},
},
defaultExpanded: true,
languages: ["curl_bash"],
// e.g. only show curl bash = ["curl_bash"]
};
export default function APIDocs() {
return (
<div>
<Head>
<title>Cal.com - Docs - SwaggerUI</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<SwaggerUI
requestSnippets={requestSnippets}
requestSnippetsEnabled={true}
docExpansion="none"
operationsSorter="method"
filter={true}
url={process.env.NEXT_PUBLIC_SWAGGER_DOCS_URL || "https://api.cal.com/api/docs"}
/>
</div>
<SwaggerUIDynamic
url={process.env.NEXT_PUBLIC_SWAGGER_DOCS_URL || "https://api.cal.com/docs"}
persistAuthorization={true}
supportedSubmitMethods={["get", "post", "delete", "put", "options", "patch"]}
requestSnippetsEnabled={true}
requestSnippets={requestSnippets}
plugins={[SnippedGenerator]}
tryItOutEnabled={true}
syntaxHighlight={true}
enableCORS={false} // Doesn't seem to work either
docExpansion="list"
filter={true}
/>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

+86
View File
@@ -14,3 +14,89 @@ a {
* {
box-sizing: border-box;
}
@media (max-width: 768px) {
.swagger-ui .opblock-tag {
font-size: 90% !important;
}
.swagger-ui .opblock .opblock-summary {
display: grid;
flex-direction: column;
}
.opblock-summary-path {
flex-shrink: 0;
max-width: 100% !important;
padding: 10px 5px !important;
}
.opblock-summary-description {
font-size: 16px !important;
padding: 0px 5px;
}
.swagger-ui .scheme-container .schemes {
align-items: center;
display: flex;
flex-direction: column;
}
.swagger-ui .info .title {
color: #3b4151;
font-family: sans-serif;
font-size: 22px;
}
.swagger-ui .scheme-container {
padding: 14px 0;
}
.swagger-ui .info {
margin: 10px 0;
}
.swagger-ui .auth-wrapper {
margin: 10px 0;
}
.swagger-ui .authorization__btn {
display: none;
}
.swagger-ui .opblock {
margin: 0 0 5px;
}
button.opblock-summary-control > svg {
display: none;
}
.swagger-ui .filter .operation-filter-input {
border: 2px solid #d8dde7;
margin: 5px 5px;
padding: 5px;
width: 100vw;
}
.swagger-ui .wrapper {
padding: 0 4px;
width: 100%;
}
.swagger-ui .info .title small {
top: 5px;
}
.swagger-ui a.nostyle, .swagger-ui a.nostyle:visited {
width: 100%;
}
div.request-snippets > div.curl-command > div:nth-child(1) {
overscroll-behavior: contain;
overflow-x: scroll;
}
.swagger-ui .opblock-body pre.microlight {
font-size: 9px;
}
.swagger-ui table tbody tr td {
padding: 0px 0 0;
vertical-align: none;
}
td.response-col_description > div > div > p {
font-size: 12px;
}
div.no-margin > div > div.responses-wrapper > div.responses-inner > div > div > table > tbody > tr {
display: flex;
width: 100vw;
flex-direction: column;
font-size: 60%;
}
div.no-margin > div > div.responses-wrapper > div.responses-inner > div > div > table > thead > tr {
display: none;
}
}
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -13,7 +13,11 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
"incremental": true,
"baseUrl": ".",
"paths": {
"@lib/*": ["lib/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
File diff suppressed because it is too large Load Diff
+3
View File
@@ -61,3 +61,6 @@ yarn-error.log*
# Typescript
tsconfig.tsbuildinfo
# Autogenerated embed content
public/embed
+30 -6
View File
@@ -8,14 +8,13 @@ import {
} from "@heroicons/react/outline";
import { ChevronLeftIcon } from "@heroicons/react/solid";
import Link from "next/link";
import React from "react";
import React, { useEffect, useState } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { App as AppType } from "@calcom/types/App";
import { Button } from "@calcom/ui";
//import NavTabs from "@components/NavTabs";
import Shell from "@components/Shell";
import Badge from "@components/ui/Badge";
@@ -60,10 +59,32 @@ export default function App({
currency: "USD",
useGrouping: false,
}).format(price);
const [installedApp, setInstalledApp] = useState(false);
useEffect(() => {
async function getInstalledApp(appCredentialType: string) {
const queryParam = new URLSearchParams();
queryParam.set("app-credential-type", appCredentialType);
try {
const result = await fetch(`/api/app-store/installed?${queryParam.toString()}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (result.status === 200) {
setInstalledApp(true);
}
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
}
}
}
getInstalledApp(type);
}, [type]);
return (
<>
<Shell large>
<Shell large isPublic>
<div className="-mx-4 md:-mx-8">
<div className="bg-gray-50 px-4">
<Link href="/apps">
@@ -73,7 +94,10 @@ export default function App({
</Link>
<div className="items-center justify-between py-4 sm:flex sm:py-8">
<div className="flex">
<img className="h-16 w-16" src={logo} alt={name} />
{
// eslint-disable-next-line @next/next/no-img-element
<img className="h-16 w-16" src={logo} alt={name} />
}
<header className="px-4 py-2">
<h1 className="font-cal text-xl text-gray-900">{name}</h1>
<h2 className="text-sm text-gray-500">
@@ -83,7 +107,7 @@ export default function App({
</div>
<div className="mt-4 sm:mt-0 sm:text-right">
{isGlobal ? (
{isGlobal || installedApp ? (
<Button color="secondary" disabled title="This app is globally installed">
{t("installed")}
</Button>
+12 -14
View File
@@ -1,30 +1,28 @@
import { useSession } from "next-auth/react";
import React from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import NavTabs from "./NavTabs";
const tabs = [
{
name: "app_store",
href: "/apps",
},
{
name: "installed_apps",
href: "/apps/installed",
},
];
export default function AppsShell({ children }: { children: React.ReactNode }) {
const { t } = useLocale();
const { status } = useSession();
const tabs = [
{
name: t("app_store"),
href: "/apps",
},
{
name: t("installed_apps"),
href: "/apps/installed",
},
];
return (
<>
<div className="mb-12 block lg:hidden">
{status === "authenticated" && <NavTabs tabs={tabs} linkProps={{ shallow: true }} />}
</div>
<main>{children}</main>
<main className="pb-6">{children}</main>
</>
);
}
+19 -18
View File
@@ -1,26 +1,27 @@
import React from "react";
import { useLocale } from "@lib/hooks/useLocale";
import NavTabs from "./NavTabs";
export default function BookingsShell({ children }: { children: React.ReactNode }) {
const { t } = useLocale();
const tabs = [
{
name: t("upcoming"),
href: "/bookings/upcoming",
},
{
name: t("past"),
href: "/bookings/past",
},
{
name: t("cancelled"),
href: "/bookings/cancelled",
},
];
const tabs = [
{
name: "upcoming",
href: "/bookings/upcoming",
},
{
name: "recurring",
href: "/bookings/recurring",
},
{
name: "past",
href: "/bookings/past",
},
{
name: "cancelled",
href: "/bookings/cancelled",
},
];
export default function BookingsShell({ children }: { children: React.ReactNode }) {
return (
<>
<NavTabs tabs={tabs} linkProps={{ shallow: true }} />
+12 -2
View File
@@ -1,6 +1,6 @@
import { useEffect } from "react";
import { useBrandColors } from "@calcom/embed-core";
import { useBrandColors } from "@calcom/embed-core/embed-iframe";
const brandColor = "#292929";
const brandTextColor = "#ffffff";
@@ -274,7 +274,17 @@ const BrandColor = ({
"--brand-text-color-dark-mode",
getContrastingTextColor(darkVal, true)
);
}, [lightVal, darkVal]);
}, [
embedBrandingColors.highlightColor,
embedBrandingColors.lightestColor,
embedBrandingColors.lighterColor,
embedBrandingColors.lightColor,
embedBrandingColors.medianColor,
embedBrandingColors.darkColor,
embedBrandingColors.darkerColor,
lightVal,
darkVal,
]);
return null;
};
+903
View File
@@ -0,0 +1,903 @@
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 } 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, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
import { trpc } from "@lib/trpc";
import NavTabs from "@components/NavTabs";
import ColorPicker from "@components/ui/colorpicker";
import Select from "@components/ui/form/Select";
type EmbedType = "inline" | "floating-popup" | "element-click";
const queryParamsForDialog = ["embedType", "tabName", "eventTypeId"];
const embeds: {
illustration: React.ReactElement;
title: string;
subtitle: string;
type: EmbedType;
}[] = [
{
title: "Inline Embed",
subtitle: "Loads your Cal scheduling page directly inline with your other website content",
type: "inline",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="2" fill="#E1E1E1" />
<rect x="24.5" y="51" width="139" height="163" rx="1.5" fill="#F8F8F8" />
<rect opacity="0.8" x="48" y="74.5" width="80" height="8" rx="2" fill="#E1E1E1" />
<rect x="48" y="86.5" width="48" height="4" rx="1" fill="#E1E1E1" />
<rect x="49" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="99.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="73" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="125.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<path
d="M61 124.5H67V122.5H61V124.5ZM68 125.5V131.5H70V125.5H68ZM67 132.5H61V134.5H67V132.5ZM60 131.5V125.5H58V131.5H60ZM61 132.5C60.4477 132.5 60 132.052 60 131.5H58C58 133.157 59.3431 134.5 61 134.5V132.5ZM68 131.5C68 132.052 67.5523 132.5 67 132.5V134.5C68.6569 134.5 70 133.157 70 131.5H68ZM67 124.5C67.5523 124.5 68 124.948 68 125.5H70C70 123.843 68.6569 122.5 67 122.5V124.5ZM61 122.5C59.3431 122.5 58 123.843 58 125.5H60C60 124.948 60.4477 124.5 61 124.5V122.5Z"
fill="#3E3E3E"
/>
<rect x="73" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="121" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="121" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="49" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="61" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="73" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="85" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="97" y="161.5" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="109" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="24.5" y="51" width="139" height="163" rx="1.5" stroke="#292929" />
<rect x="176" y="50.5" width="108" height="164" rx="2" fill="#E1E1E1" />
<rect x="24" y="226.5" width="260" height="38.5" rx="2" fill="#E1E1E1" />
{/* <path
d="M2 1H306V-1H2V1ZM307 2V263H309V2H307ZM306 264H2V266H306V264ZM1 263V1.99999H-1V263H1ZM2 264C1.44772 264 1 263.552 1 263H-1C-1 264.657 0.343147 266 2 266V264ZM307 263C307 263.552 306.552 264 306 264V266C307.657 266 309 264.657 309 263H307ZM306 1C306.552 1 307 1.44772 307 2H309C309 0.343145 307.657 -1 306 -1V1ZM2 -1C0.343151 -1 -1 0.343133 -1 1.99999H1C1 1.44771 1.44771 1 2 1V-1Z"
fill="#CFCFCF"
/> */}
</svg>
),
},
{
title: "Floating pop-up button",
subtitle: "Adds a floating button on your site that launches Cal in a dialog.",
type: "floating-popup",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="2" fill="#E1E1E1" />
<rect x="24" y="50.5" width="120" height="76" rx="2" fill="#E1E1E1" />
<rect x="24" y="138.5" width="120" height="76" rx="2" fill="#E1E1E1" />
<rect x="156" y="50.5" width="128" height="164" rx="2" fill="#E1E1E1" />
<rect x="24" y="226.5" width="260" height="38.5" rx="2" fill="#E1E1E1" />
<rect x="226" y="223.5" width="66" height="26" rx="2" fill="#292929" />
<rect x="242" y="235.5" width="34" height="2" rx="1" fill="white" />
{/* <path
d="M2 1H306V-1H2V1ZM307 2V263H309V2H307ZM306 264H2V266H306V264ZM1 263V1.99999H-1V263H1ZM2 264C1.44772 264 1 263.552 1 263H-1C-1 264.657 0.343147 266 2 266V264ZM307 263C307 263.552 306.552 264 306 264V266C307.657 266 309 264.657 309 263H307ZM306 1C306.552 1 307 1.44772 307 2H309C309 0.343145 307.657 -1 306 -1V1ZM2 -1C0.343151 -1 -1 0.343133 -1 1.99999H1C1 1.44771 1.44771 1 2 1V-1Z"
fill="#CFCFCF"
/> */}
</svg>
),
},
{
title: "Pop up via element click",
subtitle: "Open your Cal dialog when someone clicks an element.",
type: "element-click",
illustration: (
<svg
width="100%"
height="100%"
className="rounded-md"
viewBox="0 0 308 265"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
fill="white"
/>
<rect x="24" width="260" height="38.5" rx="2" fill="#E1E1E1" />
<rect x="24" y="50.5" width="120" height="76" rx="2" fill="#E1E1E1" />
<rect x="24" y="138.5" width="120" height="76" rx="2" fill="#E1E1E1" />
<rect x="156" y="50.5" width="128" height="164" rx="2" fill="#E1E1E1" />
<rect x="24" y="226.5" width="260" height="38.5" rx="2" fill="#E1E1E1" />
<rect x="84.5" y="61.5" width="139" height="141" rx="1.5" fill="#F8F8F8" />
<rect opacity="0.8" x="108" y="85" width="80" height="8" rx="2" fill="#E1E1E1" />
<rect x="108" y="97" width="48" height="4" rx="1" fill="#E1E1E1" />
<rect x="109" y="110" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="110" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="133" y="110" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="145" y="110" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="157" y="110" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="169" y="110" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="181" y="110" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="193" y="110" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="145" y="124" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="157" y="124" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="169" y="124" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="181" y="124" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="193" y="124" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="136" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="136" width="6" height="6" rx="1" fill="#3E3E3E" />
<path
d="M121 135H127V133H121V135ZM128 136V142H130V136H128ZM127 143H121V145H127V143ZM120 142V136H118V142H120ZM121 143C120.448 143 120 142.552 120 142H118C118 143.657 119.343 145 121 145V143ZM128 142C128 142.552 127.552 143 127 143V145C128.657 145 130 143.657 130 142H128ZM127 135C127.552 135 128 135.448 128 136H130C130 134.343 128.657 133 127 133V135ZM121 133C119.343 133 118 134.343 118 136H120C120 135.448 120.448 135 121 135V133Z"
fill="#3E3E3E"
/>
<rect x="133" y="136" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="145" y="136" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="157" y="136" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="169" y="136" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="181" y="136" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="193" y="136" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="148" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="148" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="148" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="145" y="148" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="157" y="148" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="169" y="148" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="181" y="148" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="193" y="148" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="160" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="160" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="160" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="145" y="160" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="157" y="160" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="169" y="160" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="181" y="160" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="193" y="160" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="109" y="172" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="121" y="172" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="133" y="172" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="145" y="172" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="157" y="172" width="6" height="6" rx="1" fill="#3E3E3E" />
<rect x="169" y="172" width="6" height="6" rx="1" fill="#C6C6C6" />
<rect x="84.5" y="61.5" width="139" height="141" rx="1.5" stroke="#292929" />
{/* <path
d="M2 1H306V-1H2V1ZM307 2V263H309V2H307ZM306 264H2V266H306V264ZM1 263V1.99999H-1V263H1ZM2 264C1.44772 264 1 263.552 1 263H-1C-1 264.657 0.343147 266 2 266V264ZM307 263C307 263.552 306.552 264 306 264V266C307.657 266 309 264.657 309 263H307ZM306 1C306.552 1 307 1.44772 307 2H309C309 0.343145 307.657 -1 306 -1V1ZM2 -1C0.343151 -1 -1 0.343133 -1 1.99999H1C1 1.44771 1.44771 1 2 1V-1Z"
fill="#CFCFCF"
/> */}
</svg>
),
},
];
function getEmbedSnippetString() {
// TODO: Import this string from @calcom/embed-snippet
return `
(function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar); return; } p(cal, ar); }; })(window, "${EMBED_LIB_URL}", "init");
Cal("init", {origin:"${WEBAPP_URL}"});
`;
}
const EmbedNavBar = () => {
const { t } = useLocale();
const tabs = [
{
name: t("Embed"),
tabName: "embed-code",
icon: CodeIcon,
},
{
name: t("Preview"),
tabName: "embed-preview",
icon: EyeIcon,
},
];
return <NavTabs data-testid="embed-tabs" tabs={tabs} linkProps={{ shallow: true }} />;
};
const ThemeSelectControl = ({
children,
...props
}: ControlProps<{ value: string; label: string }, false>) => {
return (
<components.Control {...props}>
<SunIcon className="h-[32px] w-[32px] text-gray-500" />
{children}
</components.Control>
);
};
const ChooseEmbedTypesDialogContent = () => {
const { t } = useLocale();
const router = useRouter();
return (
<DialogContent size="lg">
<div className="mb-4">
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
{t("how_you_want_add_cal_site")}
</h3>
<div>
<p className="text-sm text-gray-500">{t("choose_ways_put_cal_site")}</p>
</div>
</div>
<div className="flex">
{embeds.map((embed, index) => (
<button
className="mr-2 w-1/3 p-3 text-left hover:rounded-md hover:border hover:bg-neutral-100"
key={index}
data-testid={embed.type}
onClick={() => {
router.push({
query: {
...router.query,
embedType: embed.type,
},
});
}}>
<div className="order-none box-border flex-none rounded-sm border border-solid bg-white">
{embed.illustration}
</div>
<div className="mt-2 font-medium text-neutral-900">{embed.title}</div>
<p className="text-sm text-gray-500">{embed.subtitle}</p>
</button>
))}
</div>
</DialogContent>
);
};
const EmbedTypeCodeAndPreviewDialogContent = ({
eventTypeId,
embedType,
}: {
eventTypeId: EventType["id"];
embedType: EmbedType;
}) => {
const { t } = useLocale();
const router = useRouter();
const iframeRef = useRef<HTMLIFrameElement>(null);
const embedCode = useRef<HTMLTextAreaElement>(null);
const embed = embeds.find((embed) => embed.type === embedType);
const { data: eventType, isLoading } = trpc.useQuery([
"viewer.eventTypes.get",
{
id: +eventTypeId,
},
]);
const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true);
const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
const [previewState, setPreviewState] = useState({
inline: {
width: "100%",
height: "100%",
},
theme: "auto",
floatingPopup: {},
elementClick: {},
palette: {
brandColor: "#000000",
},
});
const close = () => {
const noPopupQuery = {
...router.query,
};
delete noPopupQuery.dialog;
queryParamsForDialog.forEach((queryParam) => {
delete noPopupQuery[queryParam];
});
router.push({
query: noPopupQuery,
});
};
// Use embed-code as default tab
if (!router.query.tabName) {
router.query.tabName = "embed-code";
router.push({
query: {
...router.query,
},
});
}
if (isLoading) {
return null;
}
if (!embed || !eventType) {
close();
return null;
}
const calLink = `${eventType.team ? `team/${eventType.team.slug}` : eventType.users[0].username}/${
eventType.slug
}`;
// TODO: Not sure how to make these template strings look better formatted.
// This exact formatting is required to make the code look nicely formatted together.
const getEmbedUIInstructionString = () =>
`Cal("ui", {
${getThemeForSnippet() ? 'theme: "' + previewState.theme + '",\n ' : ""}styles: {
branding: ${JSON.stringify(previewState.palette)}
}
})`;
const getEmbedTypeSpecificString = () => {
if (embedType === "inline") {
return `
Cal("inline", {
elementOrSelector:"#my-cal-inline",
calLink: "${calLink}"
});
${getEmbedUIInstructionString().trim()}`;
} else if (embedType === "floating-popup") {
const floatingButtonArg = {
calLink,
...previewState.floatingPopup,
};
return `
Cal("floatingButton", ${JSON.stringify(floatingButtonArg)});
${getEmbedUIInstructionString().trim()}`;
} else if (embedType === "element-click") {
return `//Important: Also, add data-cal-link="${calLink}" attribute to the element you want to open Cal on click
${getEmbedUIInstructionString().trim()}`;
}
return "";
};
const getThemeForSnippet = () => {
return previewState.theme !== "auto" ? previewState.theme : null;
};
const getDimension = (dimension: string) => {
if (dimension.match(/^\d+$/)) {
dimension = `${dimension}%`;
}
return dimension;
};
const addToPalette = (update: typeof previewState["palette"]) => {
setPreviewState((previewState) => {
return {
...previewState,
palette: {
...previewState.palette,
...update,
},
};
});
};
const previewInstruction = (instruction: { name: string; arg: unknown }) => {
iframeRef.current?.contentWindow?.postMessage(
{
mode: "cal:preview",
type: "instruction",
instruction,
},
"*"
);
};
const inlineEmbedDimensionUpdate = ({ width, height }: { width: string; height: string }) => {
iframeRef.current?.contentWindow?.postMessage(
{
mode: "cal:preview",
type: "inlineEmbedDimensionUpdate",
data: {
width: getDimension(width),
height: getDimension(height),
},
},
"*"
);
};
previewInstruction({
name: "ui",
arg: {
theme: previewState.theme,
styles: {
branding: {
...previewState.palette,
},
},
},
});
if (embedType === "floating-popup") {
previewInstruction({
name: "floatingButton",
arg: {
attributes: {
id: "my-floating-button",
},
...previewState.floatingPopup,
},
});
}
if (embedType === "inline") {
inlineEmbedDimensionUpdate({
width: previewState.inline.width,
height: previewState.inline.height,
});
}
const ThemeOptions = [
{ value: "auto", label: "Auto Theme" },
{ value: "dark", label: "Dark Theme" },
{ value: "light", label: "Light Theme" },
];
const FloatingPopupPositionOptions = [
{
value: "bottom-right",
label: "Bottom Right",
},
{
value: "bottom-left",
label: "Bottom Left",
},
];
return (
<DialogContent size="xl">
<div className="flex">
<div className="flex w-1/3 flex-col bg-white p-6">
<h3 className="mb-2 flex text-xl font-bold leading-6 text-gray-900" id="modal-title">
<button
onClick={() => {
const newQuery = { ...router.query };
delete newQuery.embedType;
delete newQuery.tabName;
router.push({
query: {
...newQuery,
},
});
}}>
<ArrowLeftIcon className="mr-4 w-4"></ArrowLeftIcon>
</button>
{embed.title}
</h3>
<hr className={classNames("mt-4", embedType === "element-click" ? "hidden" : "")}></hr>
<div className={classNames("mt-4 font-medium", embedType === "element-click" ? "hidden" : "")}>
<Collapsible
open={isEmbedCustomizationOpen}
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
<CollapsibleTrigger
type="button"
className="flex w-full items-center text-base font-medium text-neutral-900">
<div>
{embedType === "inline"
? "Inline Embed Customization"
: embedType === "floating-popup"
? "Floating Popup Customization"
: "Element Click Customization"}
</div>
<ChevronRightIcon
className={`${
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
/>
</CollapsibleTrigger>
<CollapsibleContent className="text-sm">
<div className={classNames("mt-6", embedType === "inline" ? "block" : "hidden")}>
{/*TODO: Add Auto/Fixed toggle from Figma */}
<div className="text-sm">Embed Window Sizing</div>
<div className="justify-left flex items-center">
<TextField
name="width"
labelProps={{ className: "hidden" }}
required
value={previewState.inline.width}
onChange={(e) => {
setPreviewState((previewState) => {
const width = e.target.value || "100%";
return {
...previewState,
inline: {
...previewState.inline,
width,
},
};
});
}}
addOnLeading={<InputLeading>W</InputLeading>}
/>
<span className="p-2">x</span>
<TextField
labelProps={{ className: "hidden" }}
name="height"
value={previewState.inline.height}
required
onChange={(e) => {
const height = e.target.value || "100%";
setPreviewState((previewState) => {
return {
...previewState,
inline: {
...previewState.inline,
height,
},
};
});
}}
addOnLeading={<InputLeading>H</InputLeading>}
/>
</div>
</div>
<div
className={classNames(
"mt-4 items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div className="text-sm">Button Text</div>
{/* Default Values should come from preview iframe */}
<TextField
name="buttonText"
labelProps={{ className: "hidden" }}
onChange={(e) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonText: e.target.value,
},
};
});
}}
defaultValue="Book my Cal"
required
/>
</div>
<div
className={classNames(
"mt-4 flex items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div className="text-sm">Display Calendar Icon Button</div>
<Switch
defaultChecked={true}
onCheckedChange={(checked) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
hideButtonIcon: !checked,
},
};
});
}}></Switch>
</div>
<div
className={classNames(
"mt-4 flex items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div>Position of Button</div>
<Select
onChange={(position) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonPosition: position?.value,
},
};
});
}}
defaultValue={FloatingPopupPositionOptions[0]}
options={FloatingPopupPositionOptions}></Select>
</div>
<div
className={classNames(
"mt-4 flex items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div>Button Color</div>
<div className="w-36">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonColor: color,
},
};
});
}}></ColorPicker>
</div>
</div>
<div
className={classNames(
"mt-4 flex items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div>Text Color</div>
<div className="w-36">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
setPreviewState((previewState) => {
return {
...previewState,
floatingPopup: {
...previewState.floatingPopup,
buttonTextColor: color,
},
};
});
}}></ColorPicker>
</div>
</div>
{/* <div
className={classNames(
"mt-4 items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div>Button Color on Hover</div>
<div className="w-36">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
addToPalette({
"floating-popup-button-color-hover": color,
});
}}></ColorPicker>
</div>
</div> */}
</CollapsibleContent>
</Collapsible>
</div>
<hr className="mt-4"></hr>
<div className="mt-4 font-medium">
<Collapsible
open={isBookingCustomizationOpen}
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
<CollapsibleTrigger className="flex w-full" type="button">
<div className="text-base font-medium text-neutral-900">Cal Booking Customization</div>
<ChevronRightIcon
className={`${
isBookingCustomizationOpen ? "rotate-90 transform" : ""
} ml-auto h-5 w-5 text-neutral-500`}
/>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="mt-6 text-sm">
<Label className="flex items-center justify-between">
<div>Theme</div>
<Select
className="w-36"
defaultValue={ThemeOptions[0]}
components={{
Control: ThemeSelectControl,
}}
onChange={(option) => {
if (!option) {
return;
}
setPreviewState((previewState) => {
return {
...previewState,
theme: option.value,
};
});
}}
options={ThemeOptions}></Select>
</Label>
{[
{ name: "brandColor", title: "Brand Color" },
// { name: "lightColor", title: "Light Color" },
// { name: "lighterColor", title: "Lighter Color" },
// { name: "lightestColor", title: "Lightest Color" },
// { name: "highlightColor", title: "Highlight Color" },
// { name: "medianColor", title: "Median Color" },
].map((palette) => (
<Label key={palette.name} className="flex items-center justify-between">
<div>{palette.title}</div>
<div className="w-36">
<ColorPicker
defaultValue="#000000"
onChange={(color) => {
addToPalette({
[palette.name as keyof typeof previewState["palette"]]: color,
});
}}
/>
</div>
</Label>
))}
</div>
</CollapsibleContent>
</Collapsible>
</div>
</div>
<div className="w-2/3 bg-gray-50 p-6">
<EmbedNavBar />
<div>
<div
className={classNames(router.query.tabName === "embed-code" ? "block" : "hidden", "h-[75vh]")}>
<small className="flex py-4 text-neutral-500">{t("place_where_cal_widget_appear")}</small>
<TextArea
data-testid="embed-code"
ref={embedCode}
name="embed-code"
className="h-[36rem]"
readOnly
value={
`<!-- Cal ${embedType} embed code begins -->\n` +
(embedType === "inline"
? `<div style="width:${getDimension(previewState.inline.width)};height:${getDimension(
previewState.inline.height
)};overflow:scroll" id="my-cal-inline"></div>\n`
: "") +
`<script type="text/javascript">
${getEmbedSnippetString().trim()}
${getEmbedTypeSpecificString().trim()}
</script>
<!-- Cal ${embedType} embed code ends -->`
}></TextArea>
<p className="hidden text-sm text-gray-500">
{t(
"Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options."
)}
</p>
</div>
<div className={router.query.tabName == "embed-preview" ? "block" : "hidden"}>
<iframe
ref={iframeRef}
data-testid="embed-preview"
className="border-1 h-[75vh] border"
width="100%"
height="100%"
src={`${WEBAPP_URL}/embed/preview.html?embedType=${embedType}&calLink=${calLink}`}
/>
</div>
</div>
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button
type="submit"
onClick={() => {
if (!embedCode.current) {
return;
}
navigator.clipboard.writeText(embedCode.current.value);
showToast(t("code_copied"), "success");
}}>
{t("copy_code")}
</Button>
<DialogClose asChild>
<Button color="secondary">{t("Close")}</Button>
</DialogClose>
</div>
</div>
</div>
</DialogContent>
);
};
export const EmbedDialog = () => {
const router = useRouter();
const eventTypeId: EventType["id"] = +(router.query.eventTypeId as string);
return (
<Dialog name="embed" clearQueryParamsOnClose={queryParamsForDialog}>
{!router.query.embedType ? (
<ChooseEmbedTypesDialogContent />
) : (
<EmbedTypeCodeAndPreviewDialogContent
eventTypeId={eventTypeId}
embedType={router.query.embedType as EmbedType}
/>
)}
</Dialog>
);
};
export const EmbedButton = ({
eventTypeId,
className = "",
dark,
...props
}: {
eventTypeId: EventType["id"];
className: string;
dark?: boolean;
}) => {
const { t } = useLocale();
const router = useRouter();
className = classNames(className, "hidden lg:flex");
const openEmbedModal = () => {
const query = {
...router.query,
dialog: "embed",
eventTypeId,
};
router.push(
{
pathname: router.pathname,
query,
},
undefined,
{ shallow: true }
);
};
return (
<Button
type="button"
color="minimal"
size="sm"
className={className}
{...props}
data-test-eventtype-id={eventTypeId}
data-testid={"event-type-embed"}
onClick={() => openEmbedModal()}>
<CodeIcon
className={classNames("h-4 w-4 ltr:mr-2 rtl:ml-2", dark ? "" : "text-neutral-500")}></CodeIcon>
{t("Embed")}
</Button>
);
};
+5 -5
View File
@@ -9,17 +9,17 @@ export default function EmptyScreen({
}: {
Icon: SVGComponent;
headline: string;
description: string;
description: string | React.ReactElement;
}) {
return (
<>
<div className="min-h-80 my-6 flex flex-col items-center justify-center rounded-sm border border-dashed">
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-white">
<Icon className="inline-block h-10 w-10 bg-white" />
<div className="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600 dark:bg-white">
<Icon className="inline-block h-10 w-10 text-white dark:bg-white dark:text-gray-600" />
</div>
<div className="max-w-[420px] text-center">
<h2 className="mt-6 mb-1 text-lg font-medium">{headline}</h2>
<p className="text-sm leading-6 text-gray-600">{description}</p>
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{headline}</h2>
<p className="text-sm leading-6 text-gray-600 dark:text-gray-300">{description}</p>
</div>
</div>
</>
+10 -6
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
@@ -134,7 +135,10 @@ export default function ImageUploader({
{t("no_target", { target })}
</p>
)}
{imageSrc && <img className="h-20 w-20 rounded-full" src={imageSrc} alt={target} />}
{imageSrc && (
// eslint-disable-next-line @next/next/no-img-element
<img className="h-20 w-20 rounded-full" src={imageSrc} alt={target} />
)}
</div>
)}
{result && <CropContainer imageSrc={result as string} onCropComplete={setCroppedAreaPixels} />}
+1 -7
View File
@@ -1,7 +1 @@
export default function Loader() {
return (
<div className="loader border-brand dark:border-darkmodebrand">
<span className="loader-inner bg-brand dark:bg-darkmodebrand"></span>
</div>
);
}
export { default } from "@calcom/ui/Loader";
+2
View File
@@ -3,8 +3,10 @@ export default function Logo({ small, icon }: { small?: boolean; icon?: boolean
<h1 className="inline">
<strong>
{icon ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="mx-auto w-9" alt="Cal" title="Cal" src="/cal-com-icon-white.svg" />
) : (
// eslint-disable-next-line @next/next/no-img-element
<img
className={small ? "h-4 w-auto" : "h-5 w-auto"}
alt="Cal"
+69 -29
View File
@@ -1,49 +1,89 @@
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 } from "react";
import { FC, Fragment, MouseEventHandler } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@lib/classNames";
import { SVGComponent } from "@lib/types/SVGComponent";
interface Props {
export interface NavTabProps {
tabs: {
name: string;
href: string;
icon?: ElementType;
/** If you want to change the path as per current tab */
href?: string;
/** If you want to change query param tabName as per current tab */
tabName?: string;
icon?: SVGComponent;
adminRequired?: boolean;
}[];
linkProps?: Omit<LinkProps, "href">;
}
const NavTabs: FC<Props> = ({ tabs, linkProps }) => {
const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
const router = useRouter();
const { t } = useLocale();
return (
<>
<nav
className="-mb-px flex space-x-2 space-x-5 rtl:space-x-reverse sm:rtl:space-x-reverse"
aria-label="Tabs">
className="-mb-px flex space-x-5 rtl:space-x-reverse sm:rtl:space-x-reverse"
aria-label="Tabs"
{...props}>
{tabs.map((tab) => {
const isCurrent = router.asPath === tab.href;
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;
} else if (tab.tabName) {
href = "";
isCurrent = router.query.tabName === tab.tabName;
}
const onClick: MouseEventHandler = tab.tabName
? (e) => {
e.preventDefault();
router.push({
query: {
...router.query,
tabName: tab.tabName,
},
});
}
: noop;
const Component = tab.adminRequired ? AdminRequired : Fragment;
return (
<Link key={tab.name} href={tab.href} {...linkProps}>
<a
className={classNames(
isCurrent
? "border-neutral-900 text-neutral-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium"
)}
aria-current={isCurrent ? "page" : undefined}>
{tab.icon && (
<tab.icon
className={classNames(
isCurrent ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
"-ml-0.5 hidden h-5 w-5 ltr:mr-2 rtl:ml-2 sm:inline-block"
)}
aria-hidden="true"
/>
)}
<span>{tab.name}</span>
</a>
</Link>
<Component key={tab.name}>
<Link key={tab.name} href={href} {...linkProps}>
<a
onClick={onClick}
className={classNames(
isCurrent
? "border-neutral-900 text-neutral-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium"
)}
aria-current={isCurrent ? "page" : undefined}>
{tab.icon && (
<tab.icon
className={classNames(
isCurrent ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
"-ml-0.5 hidden h-5 w-5 ltr:mr-2 rtl:ml-2 sm:inline-block"
)}
aria-hidden="true"
/>
)}
<span>{t(tab.name)}</span>
</a>
</Link>
</Component>
);
})}
</nav>
+43 -31
View File
@@ -1,42 +1,54 @@
import { CreditCardIcon, KeyIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
import React from "react";
import { CreditCardIcon, KeyIcon, LockClosedIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
import React, { ComponentProps } from "react";
import { useLocale } from "@lib/hooks/useLocale";
import ErrorBoundary from "@lib/ErrorBoundary";
import NavTabs from "./NavTabs";
import Shell from "./Shell";
export default function SettingsShell({ children }: { children: React.ReactNode }) {
const { t } = useLocale();
const tabs = [
{
name: t("profile"),
href: "/settings/profile",
icon: UserIcon,
},
{
name: t("security"),
href: "/settings/security",
icon: KeyIcon,
},
{
name: t("teams"),
href: "/settings/teams",
icon: UserGroupIcon,
},
{
name: t("billing"),
href: "/settings/billing",
icon: CreditCardIcon,
},
];
const tabs = [
{
name: "profile",
href: "/settings/profile",
icon: UserIcon,
},
{
name: "security",
href: "/settings/security",
icon: KeyIcon,
},
{
name: "teams",
href: "/settings/teams",
icon: UserGroupIcon,
},
{
name: "billing",
href: "/settings/billing",
icon: CreditCardIcon,
},
{
name: "admin",
href: "/settings/admin",
icon: LockClosedIcon,
adminRequired: true,
},
];
export default function SettingsShell({
children,
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
return (
<>
<Shell {...rest}>
<div className="sm:mx-auto">
<NavTabs tabs={tabs} />
</div>
<main className="max-w-4xl">{children}</main>
</>
<main className="max-w-4xl">
<>
<ErrorBoundary>{children}</ErrorBoundary>
</>
</main>
</Shell>
);
}
+153 -119
View File
@@ -10,16 +10,17 @@ import {
MapIcon,
MoonIcon,
ViewGridIcon,
QuestionMarkCircleIcon,
} from "@heroicons/react/solid";
import { UserPlan } from "@prisma/client";
import { SessionContextValue, signOut, useSession } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { Fragment, ReactNode, useEffect } from "react";
import React, { Fragment, ReactNode, useEffect, useState } from "react";
import { Toaster } from "react-hot-toast";
import { useIsEmbed } from "@calcom/embed-core";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { UserPlan } from "@calcom/prisma/client";
import Button from "@calcom/ui/Button";
import Dropdown, {
DropdownMenuContent,
@@ -31,30 +32,23 @@ import LicenseBanner from "@ee/components/LicenseBanner";
import TrialBanner from "@ee/components/TrialBanner";
import HelpMenuItem from "@ee/components/support/HelpMenuItem";
import ErrorBoundary from "@lib/ErrorBoundary";
import classNames from "@lib/classNames";
import { WEBAPP_URL } from "@lib/config/constants";
import { shouldShowOnboarding } from "@lib/getting-started";
import useMeQuery from "@lib/hooks/useMeQuery";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
import { trpc } from "@lib/trpc";
import CustomBranding from "@components/CustomBranding";
import Loader from "@components/Loader";
import { HeadSeo } from "@components/seo/head-seo";
import ImpersonatingBanner from "@components/ui/ImpersonatingBanner";
import pkg from "../package.json";
import { useViewerI18n } from "./I18nLanguageHandler";
import Logo from "./Logo";
export function useMeQuery() {
const meQuery = trpc.useQuery(["viewer.me"], {
retry(failureCount) {
return failureCount > 3;
},
});
return meQuery;
}
function useRedirectToLoginIfUnauthenticated(isPublic = false) {
const { data: session, status } = useSession();
const loading = status === "loading";
@@ -69,7 +63,7 @@ function useRedirectToLoginIfUnauthenticated(isPublic = false) {
router.replace({
pathname: "/auth/login",
query: {
callbackUrl: `${WEBAPP_URL}/${location.pathname}${location.search}`,
callbackUrl: `${WEBAPP_URL}${location.pathname}${location.search}`,
},
});
}
@@ -125,9 +119,10 @@ const Layout = ({
status,
plan,
...props
}: LayoutProps & { status: SessionContextValue["status"]; plan?: UserPlan }) => {
}: LayoutProps & { status: SessionContextValue["status"]; plan?: UserPlan; isLoading: boolean }) => {
const isEmbed = useIsEmbed();
const router = useRouter();
const { t } = useLocale();
const navigation = [
{
@@ -204,7 +199,7 @@ const Layout = ({
</Link>
{/* logo icon for tablet */}
<Link href="/event-types">
<a className="md:inline lg:hidden">
<a className="text-center md:inline lg:hidden">
<Logo small icon />
</a>
</Link>
@@ -213,6 +208,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"
@@ -311,6 +307,7 @@ const Layout = ({
props.flexChildrenContainer && "flex flex-1 flex-col",
!props.large && "py-8"
)}>
<ImpersonatingBanner />
{!!props.backPath && (
<div className="mx-3 mb-8 sm:mx-8">
<Button
@@ -329,10 +326,21 @@ const Layout = ({
)}>
{props.HeadingLeftIcon && <div className="ltr:mr-4">{props.HeadingLeftIcon}</div>}
<div className="mb-8 w-full">
<h1 className="font-cal mb-1 text-xl font-bold capitalize tracking-wide text-gray-900">
{props.heading}
</h1>
<p className="min-h-10 text-sm text-neutral-500 ltr:mr-4 rtl:ml-4">{props.subtitle}</p>
{props.isLoading ? (
<>
<div className="mb-1 h-6 w-24 animate-pulse rounded-md bg-gray-200"></div>
<div className="mb-1 h-6 w-32 animate-pulse rounded-md bg-gray-200"></div>
</>
) : (
<>
<h1 className="font-cal mb-1 text-xl font-bold capitalize tracking-wide text-gray-900">
{props.heading}
</h1>
<p className="min-h-10 text-sm text-neutral-500 ltr:mr-4 rtl:ml-4">
{props.subtitle}
</p>
</>
)}
</div>
{props.CTA && <div className="mb-4 flex-shrink-0">{props.CTA}</div>}
</div>
@@ -342,7 +350,7 @@ const Layout = ({
"px-4 sm:px-6 md:px-8",
props.flexChildrenContainer && "flex flex-1 flex-col"
)}>
{props.children}
<ErrorBoundary>{!props.isLoading ? props.children : props.customLoader}</ErrorBoundary>
</div>
{/* show bottom navigation for md and smaller (tablet and phones) */}
{status === "authenticated" && (
@@ -403,6 +411,7 @@ type LayoutProps = {
// use when content needs to expand with flex
flexChildrenContainer?: boolean;
isPublic?: boolean;
customLoader?: ReactNode;
};
export default function Shell(props: LayoutProps) {
@@ -423,8 +432,10 @@ export default function Shell(props: LayoutProps) {
const i18n = useViewerI18n();
const { status } = useSession();
if (i18n.status === "loading" || query.status === "loading" || isRedirectingToOnboarding || loading) {
// show spinner whilst i18n is loading to avoid language flicker
const isLoading =
i18n.status === "loading" || query.status === "loading" || isRedirectingToOnboarding || loading;
if (isLoading) {
return (
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-50">
<Loader />
@@ -437,7 +448,7 @@ export default function Shell(props: LayoutProps) {
return (
<>
<CustomBranding lightVal={user?.brandColor} darkVal={user?.darkBrandColor} />
<MemoizedLayout plan={user?.plan} status={status} {...props} />
<MemoizedLayout plan={user?.plan} status={status} {...props} isLoading={isLoading} />
</>
);
}
@@ -452,9 +463,10 @@ function UserDropdown({ small }: { small?: boolean }) {
},
});
const utils = trpc.useContext();
const [helpOpen, setHelpOpen] = useState(false);
return (
<Dropdown>
<Dropdown onOpenChange={() => setHelpOpen(false)}>
<DropdownMenuTrigger asChild>
<button className="group flex w-full cursor-pointer appearance-none items-center text-left">
<span
@@ -462,11 +474,14 @@ function UserDropdown({ small }: { small?: boolean }) {
small ? "h-8 w-8" : "h-10 w-10",
"relative flex-shrink-0 rounded-full bg-gray-300 ltr:mr-3 rtl:ml-3"
)}>
<img
className="rounded-full"
src={process.env.NEXT_PUBLIC_WEBSITE_URL + "/" + user?.username + "/avatar.png"}
alt={user?.username || "Nameless User"}
/>
{
// eslint-disable-next-line @next/next/no-img-element
<img
className="rounded-full"
src={WEBAPP_URL + "/" + user?.username + "/avatar.png"}
alt={user?.username || "Nameless User"}
/>
}
{!user?.away && (
<div className="absolute bottom-0 right-0 h-3 w-3 rounded-full border-2 border-white bg-green-500"></div>
)}
@@ -493,96 +508,115 @@ function UserDropdown({ small }: { small?: boolean }) {
</button>
</DropdownMenuTrigger>
<DropdownMenuContent portalled={true}>
<DropdownMenuItem>
<a
onClick={() => {
mutation.mutate({ away: !user?.away });
utils.invalidateQueries("viewer.me");
}}
className="flex min-w-max cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
<MoonIcon
className={classNames(
user?.away
? "text-purple-500 group-hover:text-purple-700"
: "text-gray-500 group-hover:text-gray-700",
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
)}
aria-hidden="true"
/>
{user?.away ? t("set_as_free") : t("set_as_away")}
</a>
</DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" />
{user?.username && (
<DropdownMenuItem>
<a
target="_blank"
rel="noopener noreferrer"
href={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`}
className="flex items-center px-4 py-2 text-sm text-gray-700">
<ExternalLinkIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" /> {t("view_public_page")}
</a>
</DropdownMenuItem>
{helpOpen ? (
<HelpMenuItem />
) : (
<>
<DropdownMenuItem>
<a
onClick={() => {
mutation.mutate({ away: !user?.away });
utils.invalidateQueries("viewer.me");
}}
className="flex min-w-max cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
<MoonIcon
className={classNames(
user?.away
? "text-purple-500 group-hover:text-purple-700"
: "text-gray-500 group-hover:text-gray-700",
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
)}
aria-hidden="true"
/>
{user?.away ? t("set_as_free") : t("set_as_away")}
</a>
</DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" />
{user?.username && (
<DropdownMenuItem>
<a
target="_blank"
rel="noopener noreferrer"
href={`${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}`}
className="flex items-center px-4 py-2 text-sm text-gray-700">
<ExternalLinkIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" />{" "}
{t("view_public_page")}
</a>
</DropdownMenuItem>
)}
<DropdownMenuSeparator className="h-px bg-gray-200" />
<DropdownMenuItem>
<a
href="https://cal.com/slack"
target="_blank"
rel="noreferrer"
className="flex px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900">
<svg
viewBox="0 0 2447.6 2452.5"
className={classNames(
"text-gray-500 group-hover:text-gray-700",
"mt-0.5 h-4 w-4 flex-shrink-0 ltr:mr-4 rtl:ml-4"
)}
xmlns="http://www.w3.org/2000/svg">
<g clipRule="evenodd" fillRule="evenodd">
<path
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
fill="currentColor"></path>
<path
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
fill="currentColor"></path>
<path
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
fill="currentColor"></path>
<path
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
fill="currentColor"></path>
</g>
</svg>
{t("join_our_slack")}
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a
target="_blank"
rel="noopener noreferrer"
href="https://cal.com/roadmap"
className="flex items-center px-4 py-2 text-sm text-gray-700">
<MapIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" /> {t("visit_roadmap")}
</a>
</DropdownMenuItem>
<button
className="flex w-full px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-gray-100 hover:text-gray-900"
onClick={() => setHelpOpen(true)}>
<QuestionMarkCircleIcon
className={classNames(
"text-gray-500 group-hover:text-neutral-500",
"h-5 w-5 flex-shrink-0 ltr:mr-3"
)}
aria-hidden="true"
/>
{t("help")}
</button>
<DropdownMenuSeparator className="h-px bg-gray-200" />
<DropdownMenuItem>
<a
onClick={() => signOut({ callbackUrl: "/auth/logout" })}
className="flex cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
<LogoutIcon
className={classNames(
"text-gray-500 group-hover:text-gray-700",
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
)}
aria-hidden="true"
/>
{t("sign_out")}
</a>
</DropdownMenuItem>
</>
)}
<DropdownMenuSeparator className="h-px bg-gray-200" />
<DropdownMenuItem>
<a
href="https://cal.com/slack"
target="_blank"
rel="noreferrer"
className="flex px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900">
<svg
viewBox="0 0 2447.6 2452.5"
className={classNames(
"text-gray-500 group-hover:text-gray-700",
"mt-0.5 h-4 w-4 flex-shrink-0 ltr:mr-4 rtl:ml-4"
)}
xmlns="http://www.w3.org/2000/svg">
<g clipRule="evenodd" fillRule="evenodd">
<path
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
fill="currentColor"></path>
<path
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
fill="currentColor"></path>
<path
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
fill="currentColor"></path>
<path
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
fill="currentColor"></path>
</g>
</svg>
{t("join_our_slack")}
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a
target="_blank"
rel="noopener noreferrer"
href="https://cal.com/roadmap"
className="flex items-center px-4 py-2 text-sm text-gray-700">
<MapIcon className="h-5 w-5 text-gray-500 ltr:mr-3 rtl:ml-3" /> {t("visit_roadmap")}
</a>
</DropdownMenuItem>
<HelpMenuItem />
<DropdownMenuSeparator className="h-px bg-gray-200" />
<DropdownMenuItem>
<a
onClick={() => signOut({ callbackUrl: "/auth/logout" })}
className="flex cursor-pointer px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900">
<LogoutIcon
className={classNames(
"text-gray-500 group-hover:text-gray-700",
"h-5 w-5 flex-shrink-0 ltr:mr-3 rtl:ml-3"
)}
aria-hidden="true"
/>
{t("sign_out")}
</a>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
);
+7 -4
View File
@@ -1,4 +1,3 @@
import { StarIcon } from "@heroicons/react/solid";
import Link from "next/link";
import Button from "@calcom/ui/Button";
@@ -16,11 +15,15 @@ interface AppCardProps {
export default function AppCard(props: AppCardProps) {
return (
<Link href={"/apps/" + props.slug}>
<a className="block h-full rounded-sm border border-gray-300 p-5 hover:bg-neutral-50">
<a
className="block h-full rounded-sm border border-gray-300 p-5 hover:bg-neutral-50"
data-testid={`app-store-app-card-${props.slug}`}>
<div className="flex">
<img src={props.logo} alt={props.name + " Logo"} className="mb-4 h-12 w-12 rounded-sm" />
{
// eslint-disable-next-line @next/next/no-img-element
<img src={props.logo} alt={props.name + " Logo"} className="mb-4 h-12 w-12 rounded-sm" />
}
<Button
data-testid={`app-store-app-card-${props.slug}`}
color="secondary"
className="ml-auto flex self-start"
onClick={() => {
+1 -1
View File
@@ -15,7 +15,7 @@ export default function AppStoreCategories({
return (
<div className="mb-16">
<h2 className="mb-2 text-lg font-semibold text-gray-900">{t("popular_categories")}</h2>
<div className="grid-col-1 grid gap-3 md:grid-flow-col">
<div className="grid-col-1 grid w-full gap-3 overflow-scroll sm:grid-flow-col">
{categories.map((category) => (
<Link key={category.name} href={"/apps/categories/" + category.name}>
<a
@@ -0,0 +1,41 @@
import React from "react";
import { SkeletonText } from "@calcom/ui";
import { ShellSubHeading } from "@components/Shell";
function SkeletonLoader() {
return (
<>
<ShellSubHeading title={<div className="h-6 w-32 rounded-sm bg-gray-100"></div>} />
<ul className="-mx-4 animate-pulse divide-y divide-neutral-200 rounded-sm border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem />
<SkeletonItem />
<SkeletonItem />
</ul>
</>
);
}
export default SkeletonLoader;
function SkeletonItem() {
return (
<li className="group flex w-full items-center justify-between p-3">
<div className="flex-grow truncate text-sm">
<div className="flex justify-start space-x-2">
<SkeletonText width="10" height="10"></SkeletonText>
<div className="space-y-2">
<SkeletonText height="4" width="32"></SkeletonText>
<SkeletonText height="4" width="16"></SkeletonText>
</div>
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<SkeletonText width="32" height="11"></SkeletonText>
</div>
</div>
</li>
);
}
+1 -1
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,
+9 -2
View File
@@ -29,8 +29,15 @@ export default function TwoFactor() {
<div className="mx-auto !mt-0 max-w-sm">
<p className="mb-4 text-sm text-gray-500">{t("2fa_enabled_instructions")}</p>
<input hidden type="hidden" value={value} {...methods.register("totpCode")} />
<div className="flex flex-row space-x-1">
<Input className={className} name="2fa1" inputMode="decimal" {...digits[0]} autoFocus />
<div className="flex flex-row justify-between">
<Input
className={className}
name="2fa1"
inputMode="decimal"
{...digits[0]}
autoFocus
autoComplete="one-time-code"
/>
<Input className={className} name="2fa2" inputMode="decimal" {...digits[1]} />
<Input className={className} name="2fa3" inputMode="decimal" {...digits[2]} />
<Input className={className} name="2fa4" inputMode="decimal" {...digits[3]} />
@@ -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";
@@ -46,20 +46,17 @@ export function NewScheduleButton({ name = "new-schedule" }: { name?: string })
</Button>
</DialogTrigger>
<DialogContent>
<div className="mb-4">
<div className="mb-8">
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
{t("add_new_schedule")}
</h3>
<div>
<p className="text-sm text-gray-500">{t("new_event_type_to_book_description")}</p>
</div>
</div>
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-4">
<div className="mt-3 space-y-2">
<label htmlFor="label" className="block text-sm font-medium text-gray-700">
{t("name")}
</label>
@@ -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";
@@ -14,9 +14,9 @@ import Dropdown, { DropdownMenuTrigger, DropdownMenuContent } from "@calcom/ui/D
import { defaultDayRange } from "@lib/availability";
import { weekdayNames } from "@lib/core/i18n/weekday";
import useMeQuery from "@lib/hooks/useMeQuery";
import { TimeRange } from "@lib/types/schedule";
import { useMeQuery } from "@components/Shell";
import Select from "@components/ui/form/Select";
dayjs.extend(utc);
@@ -57,7 +57,7 @@ const useOptions = () => {
t = t.add(increment, "minutes");
}
return options;
}, []);
}, [timeFormat]);
const filter = useCallback(
({ offset, limit, current }: { offset?: ConfigType; limit?: ConfigType; current?: ConfigType }) => {
@@ -0,0 +1,75 @@
import { DotsHorizontalIcon, TrashIcon } from "@heroicons/react/solid";
import Link from "next/link";
import { Fragment } from "react";
import { availabilityAsString } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Availability } from "@calcom/prisma/client";
import { Button } from "@calcom/ui";
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@calcom/ui/Dropdown";
import { inferQueryOutput } from "@lib/trpc";
export function ScheduleListItem({
schedule,
deleteFunction,
isDeleting = false,
}: {
schedule: inferQueryOutput<"viewer.availability.list">["schedules"][number];
deleteFunction: ({ scheduleId }: { scheduleId: number }) => void;
isDeleting: boolean;
}) {
const { t, i18n } = useLocale();
return (
<li key={schedule.id}>
<div className="flex items-center justify-between py-5 hover:bg-neutral-50 ltr:pl-4 rtl:pr-4 sm:ltr:pl-0 sm:rtl:pr-0">
<div className="group flex w-full items-center justify-between hover:bg-neutral-50 sm:px-6">
<Link href={"/availability/" + schedule.id}>
<a className="flex-grow truncate text-sm" title={schedule.name}>
<div>
<span className="truncate font-medium text-neutral-900">{schedule.name}</span>
{schedule.isDefault && (
<span className="ml-2 inline items-center rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-800">
{t("default")}
</span>
)}
</div>
<p className="mt-1 text-xs text-neutral-500">
{schedule.availability.map((availability: Availability) => (
<Fragment key={availability.id}>
{availabilityAsString(availability, i18n.language)}
<br />
</Fragment>
))}
</p>
</a>
</Link>
</div>
<Dropdown>
<DropdownMenuTrigger className="group mr-5 h-10 w-10 border border-transparent p-0 text-neutral-500 hover:border-gray-200">
<DotsHorizontalIcon className="h-5 w-5 group-hover:text-gray-800" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<Button
disabled={isDeleting}
onClick={() => {
deleteFunction({
scheduleId: schedule.id,
});
}}
type="button"
color="warn"
className="w-full font-normal"
StartIcon={isDeleting ? undefined : TrashIcon}
loading={isDeleting}>
{isDeleting ? t("deleting") : t("delete_schedule")}
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
</div>
</li>
);
}
@@ -0,0 +1,46 @@
import React from "react";
import { SkeletonText } from "@calcom/ui";
function SkeletonLoader() {
return (
<ul className="animate-pulse divide-y divide-neutral-200 border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem />
<SkeletonItem />
<SkeletonItem />
</ul>
);
}
export default SkeletonLoader;
function SkeletonItem() {
return (
<li className="group flex w-full items-center justify-between px-2 py-[23px] sm:px-6">
<div className="flex-grow truncate text-sm">
<div className="flex flex-col space-y-2">
<SkeletonText width="32" height="4"></SkeletonText>
<SkeletonText width="32" height="2"></SkeletonText>
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<SkeletonText width="12" height="6"></SkeletonText>
</div>
</div>
</li>
);
}
export const AvailabilitySelectSkeletonLoader = () => {
return (
<li className="group flex w-full items-center justify-between rounded-sm border border-gray-200 px-[10px] py-3">
<div className="flex-grow truncate text-sm">
<div className="flex justify-between">
<SkeletonText width="32" height="4"></SkeletonText>
<SkeletonText width="4" height="4"></SkeletonText>
</div>
</div>
</li>
);
};
+45 -8
View File
@@ -1,6 +1,6 @@
import { ExclamationIcon } from "@heroicons/react/solid";
import { SchedulingType } from "@prisma/client";
import { Dayjs } from "dayjs";
import dayjs, { Dayjs } from "dayjs";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { FC, useEffect, useState } from "react";
@@ -8,6 +8,7 @@ import React, { FC, useEffect, useState } from "react";
import { nameOfDay } from "@calcom/lib/weekday";
import classNames from "@lib/classNames";
import { timeZone } from "@lib/clock";
import { useLocale } from "@lib/hooks/useLocale";
import { useSlots } from "@lib/hooks/useSlots";
@@ -20,6 +21,7 @@ type AvailableTimesProps = {
afterBufferTime: number;
eventTypeId: number;
eventLength: number;
recurringCount: number | undefined;
eventTypeSlug: string;
slotInterval: number | null;
date: Dayjs;
@@ -27,6 +29,7 @@ type AvailableTimesProps = {
username: string | null;
}[];
schedulingType: SchedulingType | null;
seatsPerTimeSlot?: number | null;
};
const AvailableTimes: FC<AvailableTimesProps> = ({
@@ -36,11 +39,13 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
eventTypeSlug,
slotInterval,
minimumBookingNotice,
recurringCount,
timeFormat,
users,
schedulingType,
beforeBufferTime,
afterBufferTime,
seatsPerTimeSlot,
}) => {
const { t, i18n } = useLocale();
const router = useRouter();
@@ -89,6 +94,8 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
date: slot.time.format(),
type: eventTypeId,
slug: eventTypeSlug,
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
},
};
@@ -100,18 +107,48 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
bookingUrl.query.user = slot.users;
}
// If event already has an attendee add booking id
if (slot.bookingUid) {
bookingUrl.query.bookingUid = slot.bookingUid;
}
return (
<div key={slot.time.format()}>
<Link href={bookingUrl}>
<a
{/* Current there is no way to disable Next.js Links */}
{seatsPerTimeSlot && slot.attendees && slot.attendees >= seatsPerTimeSlot ? (
<div
className={classNames(
"text-bookingdarker hover:bg-brand hover:text-brandcontrast dark:hover:bg-darkmodebrand dark:hover:text-darkmodebrandcontrast mb-2 block rounded-sm border bg-white py-4 font-medium hover:text-white dark:border-transparent dark:bg-gray-600 dark:text-neutral-200 dark:hover:border-black",
"text-primary-500 mb-2 block rounded-sm border bg-white py-4 font-medium opacity-25 dark:border-transparent dark:bg-gray-600 dark:text-neutral-200 ",
brand === "#fff" || brand === "#ffffff" ? "border-brandcontrast" : "border-brand"
)}
data-testid="time">
)}>
{slot.time.format(timeFormat)}
</a>
</Link>
{seatsPerTimeSlot && <p className={`text-sm`}>{t("booking_full")}</p>}
</div>
) : (
<Link href={bookingUrl}>
<a
className={classNames(
"text-primary-500 hover:bg-brand hover:text-brandcontrast dark:hover:bg-darkmodebrand dark:hover:text-darkmodebrandcontrast mb-2 block rounded-sm border bg-white py-4 font-medium hover:text-white dark:border-transparent dark:bg-gray-600 dark:text-neutral-200 dark:hover:border-black",
brand === "#fff" || brand === "#ffffff" ? "border-brandcontrast" : "border-brand"
)}
data-testid="time">
{dayjs.tz(slot.time, timeZone()).format(timeFormat)}
{seatsPerTimeSlot && (
<p
className={`${
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
? "text-rose-600"
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} text-sm`}>
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
{seatsPerTimeSlot} {t("seats_available")}
</p>
)}
</a>
</Link>
)}
</div>
);
})}
+232 -70
View File
@@ -1,43 +1,74 @@
import { BanIcon, CheckIcon, ClockIcon, XIcon } from "@heroicons/react/outline";
import { PaperAirplaneIcon } from "@heroicons/react/outline";
import {
BanIcon,
CheckIcon,
ClockIcon,
LocationMarkerIcon,
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";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Tooltip } from "@calcom/ui/Tooltip";
import { TextArea } from "@calcom/ui/form/fields";
import { HttpError } from "@lib/core/http/error";
import { inferQueryOutput, trpc } from "@lib/trpc";
import useMeQuery from "@lib/hooks/useMeQuery";
import { LocationType } from "@lib/location";
import { parseRecurringDates } from "@lib/parseDate";
import { inferQueryInput, inferQueryOutput, trpc } from "@lib/trpc";
import { useMeQuery } from "@components/Shell";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
import TableActions, { ActionType } from "@components/ui/TableActions";
type BookingListingStatus = inferQueryInput<"viewer.bookings">["status"];
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
function BookingListItem(booking: BookingItem) {
type BookingItemProps = BookingItem & {
listingStatus: BookingListingStatus;
recurringCount?: number;
};
function BookingListItem(booking: BookingItemProps) {
// Get user so we can determine 12/24 hour format preferences
const query = useMeQuery();
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(
async (confirm: boolean) => {
let body = {
id: booking.id,
confirmed: confirm,
language: i18n.language,
reason: rejectionReason,
};
/**
* Only pass down the recurring event id when we need to confirm the entire series, which happens in
* the "Upcoming" tab, to support confirming discretionally in the "Recurring" tab.
*/
if (booking.listingStatus === "upcoming" && booking.recurringEventId !== null) {
body = Object.assign({}, body, { recurringEventId: booking.recurringEventId });
}
const res = await fetch("/api/book/confirm", {
method: "PATCH",
body: JSON.stringify({
id: booking.id,
confirmed: confirm,
language: i18n.language,
reason: rejectionReason,
}),
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
},
@@ -45,6 +76,7 @@ function BookingListItem(booking: BookingItem) {
if (!res.ok) {
throw new HttpError({ statusCode: res.status });
}
setRejectionDialogIsOpen(false);
},
{
async onSettled() {
@@ -58,15 +90,25 @@ function BookingListItem(booking: BookingItem) {
const pendingActions: ActionType[] = [
{
id: "reject",
label: t("reject"),
onClick: () => setRejectionDialogIsOpen(true),
label:
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
? t("reject_all")
: t("reject"),
onClick: () => {
setRejectionDialogIsOpen(true);
},
icon: BanIcon,
disabled: mutation.isLoading,
},
{
id: "confirm",
label: t("confirm"),
onClick: () => mutation.mutate(true),
label:
booking.listingStatus === "upcoming" && booking.recurringEventId !== null
? t("confirm_all")
: t("confirm"),
onClick: () => {
mutation.mutate(true);
},
icon: CheckIcon,
disabled: mutation.isLoading,
color: "primary",
@@ -81,19 +123,32 @@ function BookingListItem(booking: BookingItem) {
icon: XIcon,
},
{
id: "reschedule",
label: t("reschedule"),
icon: ClockIcon,
id: "edit_booking",
label: t("edit_booking"),
icon: PencilAltIcon,
actions: [
{
id: "edit",
label: t("edit_booking"),
id: "reschedule",
icon: ClockIcon,
label: t("reschedule_booking"),
href: `/reschedule/${booking.uid}`,
},
{
id: "reschedule_request",
icon: PaperAirplaneIcon,
iconClassName: "rotate-45 w-[18px] -ml-[2px]",
label: t("send_reschedule_request"),
onClick: () => setIsOpenRescheduleDialog(true),
onClick: () => {
setIsOpenRescheduleDialog(true);
},
},
{
id: "change_location",
label: t("edit_location"),
onClick: () => {
setIsOpenLocationDialog(true);
},
icon: LocationMarkerIcon,
},
],
},
@@ -110,6 +165,64 @@ function BookingListItem(booking: BookingItem) {
const startTime = dayjs(booking.startTime).format(isUpcoming ? "ddd, D MMM" : "D MMMM YYYY");
const [isOpenRescheduleDialog, setIsOpenRescheduleDialog] = useState(false);
const [isOpenSetLocationDialog, setIsOpenLocationDialog] = useState(false);
const setLocationMutation = trpc.useMutation("viewer.bookings.editLocation", {
onSuccess: () => {
showToast(t("location_updated"), "success");
setIsOpenLocationDialog(false);
utils.invalidateQueries("viewer.bookings");
},
});
const saveLocation = (newLocationType: LocationType, details: { [key: string]: string }) => {
let newLocation = newLocationType as string;
if (
newLocationType === LocationType.InPerson ||
newLocationType === LocationType.Link ||
newLocationType === LocationType.UserPhone
) {
newLocation = details[Object.keys(details)[0]];
}
setLocationMutation.mutate({ bookingId: booking.id, newLocation });
};
// Calculate the booking date(s)
let recurringStrings: string[] = [];
if (booking.recurringCount && booking.eventType.recurringEvent?.freq !== null) {
[recurringStrings] = parseRecurringDates(
{
startDate: booking.startTime,
recurringEvent: booking.eventType.recurringEvent,
recurringCount: booking.recurringCount,
},
i18n
);
}
const onClick = () => {
router.push({
pathname: "/success",
query: {
date: booking.startTime,
type: booking.eventType.id,
eventSlug: booking.eventType.slug,
user: user?.username || "",
name: booking.attendees[0] ? booking.attendees[0].name : undefined,
email: booking.attendees[0] ? booking.attendees[0].email : undefined,
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,
listingStatus: booking.listingStatus,
status: booking.status,
},
});
};
return (
<>
<RescheduleDialog
@@ -117,6 +230,12 @@ function BookingListItem(booking: BookingItem) {
setIsOpenDialog={setIsOpenRescheduleDialog}
bookingUId={booking.uid}
/>
<EditLocationDialog
booking={booking}
saveLocation={saveLocation}
isOpenDialog={isOpenSetLocationDialog}
setShowLocationModal={setIsOpenLocationDialog}
/>
{/* NOTE: Should refactor this dialog component as is being rendered multiple times */}
<Dialog open={rejectionDialogIsOpen} onOpenChange={setRejectionDialogIsOpen}>
@@ -151,60 +270,103 @@ function BookingListItem(booking: BookingItem) {
</DialogContent>
</Dialog>
<tr className="flex">
<td className="hidden whitespace-nowrap py-4 align-top ltr:pl-6 rtl:pr-6 sm:table-cell">
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
<div className="text-sm text-gray-500">
{dayjs(booking.startTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")} -{" "}
{dayjs(booking.endTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}
<tr className="flex hover:bg-neutral-50">
<td
className="hidden whitespace-nowrap align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:w-56"
onClick={onClick}>
<div className="cursor-pointer py-4">
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
<div className="text-sm text-gray-500">
{dayjs(booking.startTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")} -{" "}
{dayjs(booking.endTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}
</div>
<div className="text-sm text-gray-400">
{booking.recurringCount &&
booking.eventType?.recurringEvent?.freq &&
booking.listingStatus === "upcoming" && (
<div className="underline decoration-gray-400 decoration-dashed underline-offset-2">
<div className="flex">
<Tooltip
content={recurringStrings.map((aDate, key) => (
<p key={key}>{aDate}</p>
))}>
<p className="text-gray-600 dark:text-white">
<RefreshIcon className="mr-1 -mt-1 inline-block h-4 w-4 text-gray-400" />
{`${t("every_for_freq", {
freq: t(
`${RRuleFrequency[booking.eventType.recurringEvent.freq]
.toString()
.toLowerCase()}`
),
})} ${booking.recurringCount} ${t(
`${RRuleFrequency[booking.eventType.recurringEvent.freq]
.toString()
.toLowerCase()}`,
{ count: booking.recurringCount }
)}`}
</p>
</Tooltip>
</div>
</div>
)}
</div>
</div>
</td>
<td className={"flex-1 py-4 ltr:pl-4 rtl:pr-4" + (booking.rejected ? " line-through" : "")}>
<div className="sm:hidden">
{!booking.confirmed && !booking.rejected && (
<Tag className="mb-2 ltr:mr-2 rtl:ml-2">{t("unconfirmed")}</Tag>
)}
{!!booking?.eventType?.price && !booking.paid && (
<Tag className="mb-2 ltr:mr-2 rtl:ml-2">Pending payment</Tag>
)}
<div className="text-sm font-medium text-gray-900">
{startTime}:{" "}
<small className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
</small>
<td
className={"flex-1 ltr:pl-4 rtl:pr-4" + (booking.rejected ? " line-through" : "")}
onClick={onClick}>
<div className="cursor-pointer py-4">
<div className="sm:hidden">
{!booking.confirmed && !booking.rejected && (
<Tag className="mb-2 ltr:mr-2 rtl:ml-2">{t("unconfirmed")}</Tag>
)}
{!!booking?.eventType?.price && !booking.paid && (
<Tag className="mb-2 ltr:mr-2 rtl:ml-2">Pending payment</Tag>
)}
<div className="text-sm font-medium text-gray-900">
{startTime}:{" "}
<small className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
</small>
</div>
</div>
</div>
<div
title={booking.title}
className={classNames(
"max-w-56 truncate text-sm font-medium leading-6 text-neutral-900 md:max-w-max",
isCancelled ? "line-through" : ""
)}>
{booking.eventType?.team && <strong>{booking.eventType.team.name}: </strong>}
{booking.title}
{!!booking?.eventType?.price && !booking.paid && (
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">Pending payment</Tag>
)}
{!booking.confirmed && !booking.rejected && (
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">{t("unconfirmed")}</Tag>
)}
</div>
{booking.description && (
<div className="max-w-52 md:max-w-96 truncate text-sm text-gray-500" title={booking.description}>
&quot;{booking.description}&quot;
<div
title={booking.title}
className={classNames(
"max-w-56 truncate text-sm font-medium leading-6 text-neutral-900 md:max-w-max",
isCancelled ? "line-through" : ""
)}>
{booking.eventType?.team && <strong>{booking.eventType.team.name}: </strong>}
{booking.title}
{!!booking?.eventType?.price && !booking.paid && (
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">Pending payment</Tag>
)}
{!booking.confirmed && !booking.rejected && (
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">{t("unconfirmed")}</Tag>
)}
</div>
)}
{booking.description && (
<div
className="max-w-52 md:max-w-96 truncate text-sm text-gray-500"
title={booking.description}>
&quot;{booking.description}&quot;
</div>
)}
{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>
)}
{isCancelled && booking.rescheduled && (
<div className="mt-2 inline-block text-left text-sm md:hidden">
<RequestSentMessage />
</div>
)}
{booking.attendees.length !== 0 && (
<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">
<RequestSentMessage />
</div>
)}
</div>
</td>
<td className="whitespace-nowrap py-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4">
@@ -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 <></>;
}
+6 -40
View File
@@ -1,19 +1,20 @@
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";
import { useEffect, useRef, useState } from "react";
import { useEmbedStyles } from "@calcom/embed-core";
import { useEmbedStyles } from "@calcom/embed-core/embed-iframe";
import { useLocale } from "@calcom/lib/hooks/useLocale";
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,
@@ -0,0 +1,36 @@
import React from "react";
import { SkeletonText } from "@calcom/ui";
function SkeletonLoader() {
return (
<ul className="mt-6 animate-pulse divide-y divide-neutral-200 border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem />
<SkeletonItem />
<SkeletonItem />
</ul>
);
}
export default SkeletonLoader;
function SkeletonItem() {
return (
<li className="group flex w-full items-center justify-between px-2 py-4 sm:px-6">
<div className="flex-grow truncate text-sm">
<div className="flex">
<div className="flex flex-col space-y-2">
<SkeletonText width="16" height="5" />
<SkeletonText width="32" height="4" />
</div>
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<SkeletonText width="16" height="6" />
<SkeletonText width="32" height="6" />
</div>
</div>
</li>
);
}
+2 -1
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 = {
@@ -8,23 +8,36 @@ import {
CreditCardIcon,
GlobeIcon,
InformationCircleIcon,
LocationMarkerIcon,
RefreshIcon,
VideoCameraIcon,
} from "@heroicons/react/solid";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useContracts } from "contexts/contractsContext";
import dayjs, { Dayjs } from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
import utc from "dayjs/plugin/utc";
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
import { Frequency as RRuleFrequency } from "rrule";
import { useEmbedStyles, useIsEmbed, useIsBackgroundTransparent, sdkActionManager } from "@calcom/embed-core";
import { AppStoreLocationType, LocationObject, LocationType } from "@calcom/app-store/locations";
import {
useEmbedStyles,
useIsEmbed,
useIsBackgroundTransparent,
sdkActionManager,
useEmbedNonStylesConfig,
} from "@calcom/embed-core/embed-iframe";
import classNames from "@calcom/lib/classNames";
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { localStorage } from "@calcom/lib/webstorage";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { BASE_URL } from "@lib/config/constants";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import useTheme from "@lib/hooks/useTheme";
import { isBrandingHidden } from "@lib/isBrandingHidden";
@@ -48,6 +61,35 @@ dayjs.extend(customParseFormat);
type Props = AvailabilityTeamPageProps | AvailabilityPageProps;
export const locationKeyToString = (location: LocationObject, t: TFunction) => {
switch (location.type) {
case LocationType.InPerson:
return location.address || "In Person"; // If disabled address won't exist on the object
case LocationType.Link:
return location.link || "Link"; // If disabled link won't exist on the object
case LocationType.Phone:
return t("your_number");
case LocationType.UserPhone:
return t("phone_call");
case LocationType.GoogleMeet:
return "Google Meet";
case LocationType.Zoom:
return "Zoom";
case LocationType.Daily:
return "Cal Video";
case LocationType.Jitsi:
return "Jitsi";
case LocationType.Huddle01:
return "Huddle Video";
case LocationType.Tandem:
return "Tandem";
case LocationType.Teams:
return "Microsoft Teams";
default:
return null;
}
};
const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage, booking }: Props) => {
const router = useRouter();
const isEmbed = useIsEmbed();
@@ -56,7 +98,9 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
const { t, i18n } = useLocale();
const { contracts } = useContracts();
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
let isBackgroundTransparent = useIsBackgroundTransparent();
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const isBackgroundTransparent = useIsBackgroundTransparent();
useExposePlanGlobally(plan);
useEffect(() => {
if (eventType.metadata.smartContractAddress) {
@@ -64,7 +108,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
if (!contracts[(eventType.metadata.smartContractAddress || null) as number])
router.replace(`/${eventOwner.username}`);
}
}, [contracts, eventType.metadata.smartContractAddress, router]);
}, [contracts, eventType.metadata.smartContractAddress, eventType.users, router]);
const selectedDate = useMemo(() => {
const dateString = asStringOrNull(router.query.date);
@@ -92,6 +136,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
}
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
const [timeFormat, setTimeFormat] = useState(detectBrowserTimeFormat);
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
const telemetry = useTelemetry();
@@ -100,32 +145,42 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
telemetry.withJitsu((jitsu) =>
jitsu.track(
telemetryEventTypes.pageView,
collectPageParameters("availability", { isTeamBooking: document.URL.includes("team/") })
top !== window ? telemetryEventTypes.embedView : telemetryEventTypes.pageView,
collectPageParameters("/availability", { isTeamBooking: document.URL.includes("team/") })
)
);
}, [telemetry]);
const changeDate = (newDate: Dayjs) => {
router.replace(
{
query: {
...router.query,
date: newDate.format("YYYY-MM-DDZZ"),
const changeDate = useCallback(
(newDate: Dayjs) => {
router.replace(
{
query: {
...router.query,
date: newDate.tz(timeZone(), true).format("YYYY-MM-DDZZ"),
},
},
},
undefined,
{
shallow: true,
}
);
};
undefined,
{ shallow: true }
);
},
[router]
);
useEffect(() => {
if (
selectedDate != null &&
selectedDate?.utcOffset() !== selectedDate.clone().utcOffset(0).tz(timeZone()).utcOffset()
) {
changeDate(selectedDate.tz(timeZone(), true));
}
}, [selectedDate, changeDate]);
const handleSelectTimeZone = (selectedTimeZone: string): void => {
timeZone(selectedTimeZone);
if (selectedDate) {
changeDate(selectedDate.tz(selectedTimeZone, true));
}
timeZone(selectedTimeZone);
setIsTimeOptionsOpen(false);
};
@@ -133,6 +188,15 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
setTimeFormat(is24hClock ? "HH:mm" : "h:mma");
};
// Recurring event sidebar requires more space
const maxWidth = selectedDate
? recurringEventCount
? "max-w-6xl"
: "max-w-5xl"
: recurringEventCount
? "max-w-4xl"
: "max-w-3xl";
return (
<>
<Theme />
@@ -146,23 +210,23 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
<div>
<main
className={
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed
? classNames("m-auto", selectedDate ? "max-w-5xl" : "max-w-3xl")
: "transition-max-width mx-auto my-0 duration-500 ease-in-out md:my-24 " +
(selectedDate ? "max-w-5xl" : "max-w-3xl")
}>
? classNames(maxWidth)
: classNames("transition-max-width mx-auto my-0 duration-500 ease-in-out md:my-24", maxWidth)
)}>
{isReady && (
<div
style={availabilityDatePickerEmbedStyles}
className={classNames(
isBackgroundTransparent ? "" : "bg-white dark:bg-gray-800 sm:dark:border-gray-600",
"border-bookinglightest rounded-sm md:border",
isEmbed ? "mx-auto" : selectedDate ? "max-w-5xl" : "max-w-3xl"
"border-bookinglightest rounded-md md:border",
isEmbed ? "mx-auto" : maxWidth
)}>
{/* mobile: details */}
<div className="block p-4 sm:p-8 md:hidden">
<div className="block items-center sm:flex sm:space-x-4">
<div>
<AvatarGroup
border="border-2 dark:border-gray-800 border-white"
items={
@@ -172,7 +236,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
.filter((user) => user.name !== profile.name)
.map((user) => ({
title: user.name,
image: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}/avatar.png`,
image: `${CAL_URL}/${user.username}/avatar.png`,
alt: user.name || undefined,
})),
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
@@ -180,20 +244,43 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
size={9}
truncateAfter={5}
/>
<div className="mt-4 sm:-mt-2">
<p className="text-sm font-medium text-black dark:text-white">{profile.name}</p>
<div className="mt-2 flex gap-2 text-xl font-medium dark:text-gray-100">
{eventType.title}
<div className="mt-4">
<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 break-words text-xl font-semibold dark:text-white">
{eventType.title}
</h1>
{eventType?.description && (
<p className="mb-2 text-gray-600 dark:text-white">
<p className="text-bookinglight mb-2 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{eventType.description}
</p>
)}
<div>
<ClockIcon className="mr-[10px] -mt-1 inline-block h-4 w-4" />
{eventType.locations.length === 1 && (
<p className="text-bookinglight mb-2 dark:text-white">
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
{eventType.locations.length === 1 && (
<p className="text-bookinglight mb-2 dark:text-white">
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
) : (
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
)}
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
<p className="text-bookinglight mb-2 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
{eventType.length} {t("minutes")}
</div>
</p>
{eventType.price > 0 && (
<div className="text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 dark:text-gray-400" />
@@ -206,6 +293,22 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
</IntlProvider>
</div>
)}
<div className="md:hidden">
{booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-8 mb-2 text-gray-600 dark:text-white"
data-testid="former_time_p_mobile">
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{typeof booking.startTime === "string" &&
parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
</div>
</div>
</div>
</div>
@@ -214,8 +317,8 @@ 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 " +
(selectedDate ? "sm:w-1/3" : "sm:w-1/2")
"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
border="border-2 dark:border-gray-800 border-white"
@@ -227,27 +330,88 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
.map((user) => ({
title: user.name,
alt: user.name,
image: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user.username}/avatar.png`,
image: `${CAL_URL}/${user.username}/avatar.png`,
})),
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
}
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 && (
<p className="mb-2 text-gray-600 dark:text-white">
<p className="text-bookinglight mb-3 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
{eventType.description}
</p>
)}
<p className="mb-1 -ml-2 px-2 py-1 text-gray-600 dark:text-white">
{eventType.locations.length === 1 && (
<p className="text-bookinglight mb-2 dark:text-white">
{Object.values(AppStoreLocationType).includes(
eventType.locations[0].type as unknown as AppStoreLocationType
) ? (
<VideoCameraIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
) : (
<LocationMarkerIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
)}
{locationKeyToString(eventType.locations[0], t)}
</p>
)}
{eventType.locations.length > 1 && (
<div className="text-bookinglight flex-warp mb-2 flex dark:text-white">
<div className="mr-[10px] ml-[2px] -mt-1 ">
<LocationMarkerIcon className="inline-block h-4 w-4 text-gray-400" />
</div>
<p>
{eventType.locations.map((el, i, arr) => {
return (
<span key={el.type}>
{locationKeyToString(el, t)}{" "}
{arr.length - 1 !== i && (
<span className="font-light"> {t("or_lowercase")} </span>
)}
</span>
);
})}
</p>
</div>
)}
<p className="text-bookinglight mb-3 dark:text-white">
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
{eventType.length} {t("minutes")}
</p>
{!rescheduleUid && eventType.recurringEvent?.count && eventType.recurringEvent?.freq && (
<div className="mb-3 text-gray-600 dark:text-white">
<RefreshIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="mb-1 -ml-2 inline px-2 py-1">
{t("every_for_freq", {
freq: t(
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
),
})}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-16 rounded-sm border-gray-300 bg-white text-gray-600 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500 dark:bg-gray-600 dark:text-white sm:text-sm"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline text-gray-600 dark:text-white">
{t(`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
count: recurringEventCount,
})}
</p>
</div>
)}
{eventType.price > 0 && (
<p className="mb-1 -ml-2 px-2 py-1 text-gray-600 dark:text-white">
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
@@ -260,12 +424,11 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
</IntlProvider>
</p>
)}
<TimezoneDropdown />
{previousPage === `${BASE_URL}/${profile.slug}` && (
{previousPage === `${WEBAPP_URL}/${profile.slug}` && (
<div className="flex h-full flex-col justify-end">
<ArrowLeftIcon
className="h-4 w-4 text-black transition-opacity hover:cursor-pointer dark:text-white"
className="h-4 w-4 text-black transition-opacity hover:cursor-pointer dark:text-white"
onClick={() => router.back()}
/>
<p className="sr-only">Go Back</p>
@@ -273,7 +436,9 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
)}
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-gray-600 dark:text-white" data-testid="former_time_p">
<p
className="mt-4 mb-3 text-gray-600 dark:text-white"
data-testid="former_time_p_desktop">
{t("former_time")}
</p>
<p className="text-gray-500 line-through dark:text-white">
@@ -283,6 +448,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
</div>
)}
</div>
<DatePicker
date={selectedDate}
periodType={eventType?.periodType}
@@ -309,11 +475,13 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
eventTypeSlug={eventType.slug}
slotInterval={eventType.slotInterval}
eventLength={eventType.length}
recurringCount={recurringEventCount}
date={selectedDate}
users={eventType.users}
schedulingType={eventType.schedulingType ?? null}
beforeBufferTime={eventType.beforeEventBuffer}
afterBufferTime={eventType.afterEventBuffer}
seatsPerTimeSlot={eventType.seatsPerTimeSlot}
/>
)}
</div>
+260 -70
View File
@@ -2,9 +2,12 @@ import {
CalendarIcon,
ClockIcon,
CreditCardIcon,
ExclamationCircleIcon,
ExclamationIcon,
InformationCircleIcon,
RefreshIcon,
} from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod";
import { EventTypeCustomInputType } from "@prisma/client";
import { useContracts } from "contexts/contractsContext";
import dayjs from "dayjs";
@@ -17,22 +20,31 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { FormattedNumber, IntlProvider } from "react-intl";
import { ReactMultiEmail } from "react-multi-email";
import { useMutation } from "react-query";
import { Frequency as RRuleFrequency } from "rrule";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
import { useIsEmbed, useIsBackgroundTransparent } from "@calcom/embed-core";
import {
useEmbedNonStylesConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { createPaymentLink } from "@calcom/stripe/client";
import { Button } from "@calcom/ui/Button";
import { Tooltip } from "@calcom/ui/Tooltip";
import { EmailInput, Form } from "@calcom/ui/form/fields";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
import useTheme from "@lib/hooks/useTheme";
import { LocationType } from "@lib/location";
import { LocationObject, LocationType } from "@lib/location";
import createBooking from "@lib/mutations/bookings/create-booking";
import { parseDate } from "@lib/parseDate";
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
import { parseDate, parseRecurringDates } from "@lib/parseDate";
import slugify from "@lib/slugify";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
@@ -41,14 +53,24 @@ import AvatarGroup from "@components/ui/AvatarGroup";
import type PhoneInputType from "@components/ui/form/PhoneInput";
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")
) as unknown as typeof PhoneInputType;
type BookingPageProps = BookPageProps | TeamBookingPageProps;
type BookingPageProps = BookPageProps | TeamBookingPageProps | HashLinkPageProps;
type BookingFormValues = {
name: string;
@@ -57,8 +79,9 @@ type BookingFormValues = {
locationType?: LocationType;
guests?: string[];
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;
};
};
@@ -67,34 +90,50 @@ const BookingPage = ({
booking,
profile,
isDynamicGroupBooking,
recurringEventCount,
locationLabels,
hasHashedBookingLink,
hashedLink,
}: BookingPageProps) => {
const { t, i18n } = useLocale();
const isEmbed = useIsEmbed();
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const router = useRouter();
const { contracts } = useContracts();
const { data: session } = useSession();
const isBackgroundTransparent = useIsBackgroundTransparent();
const telemetry = useTelemetry();
useEffect(() => {
telemetry.withJitsu((jitsu) =>
jitsu.track(
top !== window ? telemetryEventTypes.embedView : telemetryEventTypes.pageView,
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
)
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (eventType.metadata.smartContractAddress) {
const eventOwner = eventType.users[0];
if (!contracts[(eventType.metadata.smartContractAddress || null) as number])
/* @ts-ignore */
router.replace(`/${eventOwner.username}`);
}
}, [contracts, eventType.metadata.smartContractAddress, router]);
}, [contracts, eventType.metadata.smartContractAddress, eventType.users, router]);
const mutation = useMutation(createBooking, {
onSuccess: async (responseData) => {
const { attendees, paymentUid } = responseData;
const { id, attendees, paymentUid } = responseData;
if (paymentUid) {
return await router.push(
createPaymentLink({
paymentUid,
date,
name: attendees[0].name,
email: attendees[0].email,
absolute: false,
})
);
@@ -122,6 +161,39 @@ const BookingPage = ({
email: attendees[0].email,
location,
eventName: profile.eventName || "",
bookingId: id,
},
});
},
});
const recurringMutation = useMutation(createRecurringBooking, {
onSuccess: async (responseData = []) => {
const { attendees = [], id, recurringEventId } = responseData[0] || {};
const location = (function humanReadableLocation(location) {
if (!location) {
return;
}
if (location.includes("integration")) {
return t("web_conferencing_details_to_follow");
}
return location;
})(responseData[0].location);
return router.push({
pathname: "/success",
query: {
date,
type: eventType.id,
eventSlug: eventType.slug,
recur: recurringEventId,
user: profile.slug,
reschedule: !!rescheduleUid,
name: attendees[0].name,
email: attendees[0].email,
location,
eventName: profile.eventName || "",
bookingId: id,
},
});
},
@@ -135,10 +207,9 @@ const BookingPage = ({
const eventTypeDetail = { isWeb3Active: false, ...eventType };
type Location = { type: LocationType; address?: string; link?: string };
// it would be nice if Prisma at some point in the future allowed for Json<Location>; as of now this is not the case.
const locations: Location[] = useMemo(
() => (eventType.locations as Location[]) || [],
const locations: LocationObject[] = useMemo(
() => (eventType.locations as LocationObject[]) || [],
[eventType.locations]
);
@@ -148,10 +219,8 @@ const BookingPage = ({
}
}, [router.query.guest]);
const telemetry = useTelemetry();
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)
: [];
@@ -179,16 +248,35 @@ 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]
: "",
}),
{}
),
};
};
const bookingFormSchema = z
.object({
name: z.string().min(1),
email: z.string().email(),
})
.passthrough();
const bookingForm = useForm<BookingFormValues>({
defaultValues: defaultValues(),
resolver: zodResolver(bookingFormSchema), // Since this isn't set to strict we only validate the fields in the schema
});
const selectedLocation = useWatch({
@@ -204,7 +292,9 @@ const BookingPage = ({
})(),
});
const getLocationValue = (booking: Pick<BookingFormValues, "locationType" | "phone">) => {
const getLocationValue = (
booking: Pick<BookingFormValues, "locationType" | "phone" | "hostPhoneNumber">
) => {
const { locationType } = booking;
switch (locationType) {
case LocationType.Phone: {
@@ -216,16 +306,33 @@ const BookingPage = ({
case LocationType.Link: {
return locationInfo(locationType)?.link || "";
}
case LocationType.UserPhone: {
return locationInfo(locationType)?.hostPhoneNumber || "";
}
// Catches all other location types, such as Google Meet, Zoom etc.
default:
return selectedLocation || "";
}
};
// Calculate the booking date(s)
let recurringStrings: string[] = [],
recurringDates: Date[] = [];
if (eventType.recurringEvent?.freq && recurringEventCount !== null) {
[recurringStrings, recurringDates] = parseRecurringDates(
{
startDate: date,
recurringEvent: eventType.recurringEvent,
recurringCount: parseInt(recurringEventCount.toString()),
},
i18n
);
}
const bookEvent = (booking: BookingFormValues) => {
telemetry.withJitsu((jitsu) =>
jitsu.track(
telemetryEventTypes.bookingConfirmed,
top !== window ? telemetryEventTypes.embedBookingConfirmed : telemetryEventTypes.bookingConfirmed,
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
)
);
@@ -244,38 +351,74 @@ const BookingPage = ({
{}
);
let web3Details;
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],
};
}
mutation.mutate({
...booking,
web3Details,
start: dayjs(date).format(),
end: dayjs(date).add(eventType.length, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
timeZone: timeZone(),
language: i18n.language,
rescheduleUid,
user: router.query.user,
location: getLocationValue(
booking.locationType ? booking : { ...booking, locationType: selectedLocation }
),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))!.label,
value: booking.customInputs![inputId],
})),
});
if (recurringDates.length) {
// Identify set of bookings to one intance of recurring event to support batch changes
const recurringEventId = uuidv4();
const recurringBookings = recurringDates.map((recurringDate) => ({
...booking,
web3Details,
start: dayjs(recurringDate).format(),
end: dayjs(recurringDate).add(eventType.length, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
recurringEventId,
// Added to track down the number of actual occurrences selected by the user
recurringCount: recurringDates.length,
timeZone: timeZone(),
language: i18n.language,
rescheduleUid,
user: router.query.user,
location: getLocationValue(
booking.locationType ? booking : { ...booking, locationType: selectedLocation }
),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
})),
hasHashedBookingLink,
hashedLink,
}));
recurringMutation.mutate(recurringBookings);
} else {
mutation.mutate({
...booking,
web3Details,
start: dayjs(date).format(),
end: dayjs(date).add(eventType.length, "minute").format(),
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
timeZone: timeZone(),
language: i18n.language,
rescheduleUid,
bookingUid: router.query.bookingUid as string,
user: router.query.user,
location: getLocationValue(
booking.locationType ? booking : { ...booking, locationType: selectedLocation }
),
metadata,
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "",
value: booking.customInputs && inputId in booking.customInputs ? booking.customInputs[inputId] : "",
})),
hasHashedBookingLink,
hashedLink,
});
}
};
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>
@@ -298,16 +441,17 @@ const BookingPage = ({
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
<main
className={classNames(
isEmbed ? "mx-auto" : "mx-auto my-0 rounded-sm sm:my-24",
"max-w-3xl sm:border sm:dark:border-gray-600"
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "" : "sm:my-24",
"my-0 max-w-3xl "
)}>
{isReady && (
<div
className={classNames(
"overflow-hidden",
"main overflow-hidden",
isEmbed ? "" : "border border-gray-200",
isBackgroundTransparent ? "" : "bg-white dark:border-0 dark:bg-gray-800",
"sm:rounded-sm"
isBackgroundTransparent ? "" : "dark:border-1 bg-white dark:bg-gray-800",
"rounded-md dark:border-gray-600 sm:border"
)}>
<div className="px-4 py-5 sm:flex sm:p-4">
<div className="sm:w-1/2 sm:border-r sm:dark:border-gray-700">
@@ -329,6 +473,21 @@ const BookingPage = ({
<h1 className="text-bookingdark mb-4 text-xl font-semibold dark:text-white">
{eventType.title}
</h1>
{eventType.seatsPerTimeSlot && (
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} mb-2`}>
{booking
? eventType.seatsPerTimeSlot - booking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
</p>
)}
{eventType?.description && (
<p className="text-bookinglight mb-2 dark:text-white">
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
@@ -351,10 +510,41 @@ const BookingPage = ({
</IntlProvider>
</p>
)}
<p className="text-bookinghighlight mb-4">
<CalendarIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{parseDate(date, i18n)}
</p>
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="mb-3 text-gray-600 dark:text-white">
<RefreshIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
<p className="mb-1 -ml-2 inline px-2 py-1">
{`${t("every_for_freq", {
freq: t(`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`),
})} ${recurringEventCount} ${t(
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`,
{ count: parseInt(recurringEventCount.toString()) }
)}`}
</p>
</div>
)}
<div className="text-bookinghighlight mb-4 flex">
<CalendarIcon className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
<div className="-mt-1">
{(rescheduleUid || !eventType.recurringEvent.freq) &&
parseDate(dayjs(date).tz(timeZone()), i18n)}
{!rescheduleUid &&
eventType.recurringEvent.freq &&
recurringStrings.slice(0, 5).map((aDate, key) => <p key={key}>{aDate}</p>)}
{!rescheduleUid && eventType.recurringEvent.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((aDate, key) => (
<p key={key}>{aDate}</p>
))}>
<p className="text-gray-600 dark:text-white">
{t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
)}
</div>
</div>
{eventTypeDetail.isWeb3Active && eventType.metadata.smartContractAddress && (
<p className="text-bookinglight mb-1 -ml-2 px-2 py-1">
{t("requires_ownership_of_a_token") + " " + eventType.metadata.smartContractAddress}
@@ -372,7 +562,7 @@ const BookingPage = ({
</div>
)}
</div>
<div className="sm:w-1/2 sm:pl-8 sm:pr-4">
<div className="mt-8 sm:w-1/2 sm:pl-8 sm:pr-4">
<Form form={bookingForm} handleSubmit={bookEvent}>
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-white">
@@ -380,15 +570,12 @@ const BookingPage = ({
</label>
<div className="mt-1">
<input
{...bookingForm.register("name")}
{...bookingForm.register("name", { required: true })}
type="text"
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}
/>
@@ -405,13 +592,21 @@ const BookingPage = ({
{...bookingForm.register("email")}
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" : ""
inputClassName,
bookingForm.formState.errors.email
? "border-red-700 focus:ring-red-700"
: " border-gray-300 dark:border-gray-900"
)}
placeholder="you@example.com"
type="search" // Disables annoying 1password intrusive popup (non-optimal, I know I know...)
disabled={disableInput}
/>
{bookingForm.formState.errors.email && (
<div className="mt-2 flex items-center text-sm text-red-700 ">
<ExclamationCircleIcon className="mr-2 h-3 w-3" />
<p>{t("email_validation_error")}</p>
</div>
)}
</div>
</div>
{locations.length > 1 && (
@@ -427,7 +622,6 @@ const BookingPage = ({
{...bookingForm.register("locationType", { required: true })}
value={location.type}
defaultChecked={selectedLocation === location.type}
disabled={disableInput}
/>
<span className="text-sm ltr:ml-2 rtl:mr-2 dark:text-gray-500">
{locationLabels[location.type]}
@@ -473,12 +667,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 && (
@@ -488,9 +679,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 && (
@@ -500,8 +691,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 && (
@@ -512,8 +704,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}
@@ -600,12 +793,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">
@@ -3,15 +3,16 @@ import { CheckIcon } from "@heroicons/react/solid";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import React, { PropsWithChildren, ReactNode } 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 ConfirmationDialogContentProps = {
confirmBtn?: ReactNode;
confirmBtnText?: string;
cancelBtnText?: string;
isLoading?: boolean;
loadingText?: string;
onConfirm?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
title: string;
variety?: "danger" | "warning" | "success";
@@ -25,6 +26,8 @@ export default function ConfirmationDialogContent(props: PropsWithChildren<Confi
confirmBtn = null,
confirmBtnText = t("confirm"),
cancelBtnText = t("cancel"),
loadingText = t("loading"),
isLoading = false,
onConfirm,
children,
} = props;
@@ -59,10 +62,14 @@ export default function ConfirmationDialogContent(props: PropsWithChildren<Confi
</div>
</div>
<div className="mt-5 flex flex-row-reverse gap-x-2 sm:mt-8">
<DialogClose onClick={onConfirm} asChild>
{confirmBtn || <Button color="primary">{confirmBtnText}</Button>}
<DialogClose disabled={isLoading} onClick={onConfirm} asChild>
{confirmBtn || (
<Button color="primary" loading={isLoading}>
{isLoading ? loadingText : confirmBtnText}
</Button>
)}
</DialogClose>
<DialogClose asChild>
<DialogClose disabled={isLoading} asChild>
<Button color="secondary">{cancelBtnText}</Button>
</DialogClose>
</div>
@@ -0,0 +1,75 @@
import { ExclamationIcon } from "@heroicons/react/outline";
import { CheckIcon } from "@heroicons/react/solid";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import React, { PropsWithChildren } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/Button";
import { DialogClose, DialogContent } from "@calcom/ui/Dialog";
export type DeleteStripeDialogContentProps = {
cancelAllBookingsBtnText?: string;
removeBtnText?: string;
cancelBtnText?: string;
onConfirm?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onRemove?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
title: string;
variety?: "danger" | "warning" | "success";
};
export default function DeleteStripeDialogContent(props: PropsWithChildren<DeleteStripeDialogContentProps>) {
const { t } = useLocale();
const {
title,
variety,
cancelAllBookingsBtnText,
removeBtnText,
cancelBtnText = t("cancel"),
onConfirm,
onRemove,
children,
} = props;
return (
<DialogContent>
<div className="flex">
{variety && (
<div className="mt-0.5 ltr:mr-3">
{variety === "danger" && (
<div className="mx-auto rounded-full bg-red-100 p-2 text-center">
<ExclamationIcon className="h-5 w-5 text-red-600" />
</div>
)}
{variety === "warning" && (
<div className="mx-auto rounded-full bg-orange-100 p-2 text-center">
<ExclamationIcon className="h-5 w-5 text-orange-600" />
</div>
)}
{variety === "success" && (
<div className="mx-auto rounded-full bg-green-100 p-2 text-center">
<CheckIcon className="h-5 w-5 text-green-600" />
</div>
)}
</div>
)}
<div>
<DialogPrimitive.Title className="font-cal text-xl text-gray-900">{title}</DialogPrimitive.Title>
<DialogPrimitive.Description className="text-sm text-neutral-500">
{children}
</DialogPrimitive.Description>
</div>
</div>
<div className="mt-5 flex flex-row-reverse gap-x-2 sm:mt-8">
<DialogClose onClick={onConfirm} asChild>
<Button color="alert">{cancelAllBookingsBtnText}</Button>
</DialogClose>
<DialogClose onClick={onRemove} asChild>
<Button color="alert2">{removeBtnText}</Button>
</DialogClose>
<DialogClose asChild>
<Button color="secondary">{cancelBtnText}</Button>
</DialogClose>
</div>
</DialogContent>
);
}
@@ -0,0 +1,336 @@
import { LocationMarkerIcon } from "@heroicons/react/solid";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import dynamic from "next/dynamic";
import { useEffect } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui";
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
import { Form } from "@calcom/ui/form/fields";
import { QueryCell } from "@lib/QueryCell";
import { linkValueToString } from "@lib/linkValueToString";
import { LocationType } from "@lib/location";
import { LocationOptionsToString } from "@lib/locationOptions";
import { inferQueryOutput, trpc } from "@lib/trpc";
import CheckboxField from "@components/ui/form/CheckboxField";
import type PhoneInputType from "@components/ui/form/PhoneInput";
import Select from "@components/ui/form/Select";
const PhoneInput = dynamic(
() => import("@components/ui/form/PhoneInput")
) as unknown as typeof PhoneInputType;
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
type OptionTypeBase = {
label: string;
value: LocationType;
disabled?: boolean;
};
type LocationFormValues = {
locationType: LocationType;
locationAddress?: string;
locationLink?: string;
locationPhoneNumber?: string;
displayLocationPublicly?: boolean;
};
interface ISetLocationDialog {
saveLocation: (newLocationType: LocationType, details: { [key: string]: string }) => void;
selection?: OptionTypeBase;
booking?: BookingItem;
defaultValues?: {
type: LocationType;
address?: string | undefined;
link?: string | undefined;
hostPhoneNumber?: string | undefined;
displayLocationPublicly?: boolean | undefined;
}[];
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
isOpenDialog: boolean;
setSelectedLocation?: (param: OptionTypeBase | undefined) => void;
}
export const EditLocationDialog = (props: ISetLocationDialog) => {
const {
saveLocation,
selection,
booking,
setShowLocationModal,
isOpenDialog,
defaultValues,
setSelectedLocation,
} = props;
const { t } = useLocale();
const locationsQuery = trpc.useQuery(["viewer.locationOptions"]);
useEffect(() => {
if (selection) {
locationFormMethods.setValue("locationType", selection?.value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selection]);
const locationFormSchema = z.object({
locationType: z.string(),
locationAddress: z.string().optional(),
locationLink: z.string().url().optional(),
displayLocationPublicly: z.boolean().optional(),
locationPhoneNumber: z
.string()
.refine((val) => isValidPhoneNumber(val))
.optional(),
});
const locationFormMethods = useForm<LocationFormValues>({
mode: "onSubmit",
resolver: zodResolver(locationFormSchema),
});
const selectedLocation = useWatch({
control: locationFormMethods.control,
name: "locationType",
});
const LocationOptions =
selectedLocation === LocationType.InPerson ? (
<>
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">
{t("set_address_place")}
</label>
<div className="mt-1">
<input
type="text"
{...locationFormMethods.register("locationAddress")}
id="address"
required
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={
defaultValues
? defaultValues.find(
(location: { type: LocationType }) => location.type === LocationType.InPerson
)?.address
: undefined
}
/>
</div>
{!booking && (
<div className="mt-3">
<Controller
name="displayLocationPublicly"
control={locationFormMethods.control}
render={() => (
<CheckboxField
defaultChecked={
defaultValues
? defaultValues.find((location) => location.type === LocationType.InPerson)
?.displayLocationPublicly
: undefined
}
description={t("display_location_label")}
onChange={(e) =>
locationFormMethods.setValue("displayLocationPublicly", e.target.checked)
}
informationIconText={t("display_location_info_badge")}></CheckboxField>
)}
/>
</div>
)}
</div>
</>
) : selectedLocation === LocationType.Link ? (
<div>
<label htmlFor="link" className="block text-sm font-medium text-gray-700">
{t("set_link_meeting")}
</label>
<div className="mt-1">
<input
type="text"
{...locationFormMethods.register("locationLink")}
id="link"
required
className="block w-full rounded-sm border-gray-300 sm:text-sm"
defaultValue={
defaultValues
? defaultValues.find(
(location: { type: LocationType }) => location.type === LocationType.Link
)?.link
: undefined
}
/>
{locationFormMethods.formState.errors.locationLink && (
<p className="mt-1 text-sm text-red-500">{t("url_start_with_https")}</p>
)}
</div>
{!booking && (
<div className="mt-3">
<Controller
name="displayLocationPublicly"
control={locationFormMethods.control}
render={() => (
<CheckboxField
description={t("display_location_label")}
defaultChecked={
defaultValues
? defaultValues.find((location) => location.type === LocationType.Link)
?.displayLocationPublicly
: undefined
}
onChange={(e) => locationFormMethods.setValue("displayLocationPublicly", e.target.checked)}
informationIconText={t("display_location_info_badge")}></CheckboxField>
)}
/>
</div>
)}
</div>
) : selectedLocation === LocationType.UserPhone ? (
<div>
<label htmlFor="phonenumber" className="block text-sm font-medium text-gray-700">
{t("set_your_phone_number")}
{locationFormMethods.formState?.errors?.locationPhoneNumber?.message}
</label>
<div className="mt-1">
<PhoneInput<LocationFormValues>
control={locationFormMethods.control}
name="locationPhoneNumber"
required
id="locationPhoneNumber"
placeholder={t("host_phone_number")}
defaultValue={
defaultValues
? defaultValues.find(
(location: { type: LocationType }) => location.type === LocationType.UserPhone
)?.hostPhoneNumber
: undefined
}
/>
{locationFormMethods.formState.errors.locationPhoneNumber && (
<p className="mt-1 text-sm text-red-500">Invalid input</p>
)}
</div>
</div>
) : (
<p className="text-sm">{LocationOptionsToString(selectedLocation, t)}</p>
);
return (
<Dialog open={isOpenDialog}>
<DialogContent asChild>
<div className="inline-block transform rounded-sm bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<LocationMarkerIcon className="text-primary-600 h-6 w-6" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{t("edit_location")}
</h3>
{!booking && (
<p className="text-sm text-gray-400">{t("this_input_will_shown_booking_this_event")}</p>
)}
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"></div>
</div>
{booking && (
<>
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
<p className="mb-2 ml-1 text-sm text-black">{linkValueToString(booking.location, t)}</p>
</>
)}
<Form
form={locationFormMethods}
handleSubmit={async (values) => {
const { locationType: newLocation, displayLocationPublicly } = values;
let details = {};
if (newLocation === LocationType.InPerson) {
details = {
address: values.locationAddress,
displayLocationPublicly,
};
}
if (newLocation === LocationType.Link) {
details = { link: values.locationLink, displayLocationPublicly };
}
if (newLocation === LocationType.UserPhone) {
details = { hostPhoneNumber: values.locationPhoneNumber };
}
saveLocation(newLocation, details);
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister([
"locationType",
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
}}>
<QueryCell
query={locationsQuery}
success={({ data: locationOptions }) => {
if (!locationOptions.length) return null;
return (
<Controller
name="locationType"
control={locationFormMethods.control}
render={() => (
<Select
maxMenuHeight={150}
name="location"
defaultValue={selection}
options={
booking
? locationOptions.filter((location) => location.value !== "phone")
: locationOptions
}
isSearchable={false}
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
locationFormMethods.unregister([
"locationLink",
"locationAddress",
"locationPhoneNumber",
]);
locationFormMethods.clearErrors([
"locationLink",
"locationPhoneNumber",
"locationAddress",
]);
setSelectedLocation?.(val);
}
}}
/>
)}
/>
);
}}
/>
{selectedLocation && LocationOptions}
<div className="mt-4 flex justify-end space-x-2">
<Button
onClick={() => {
setShowLocationModal(false);
setSelectedLocation?.(undefined);
locationFormMethods.unregister("locationType");
}}
type="button"
color="secondary">
{t("cancel")}
</Button>
<Button type="submit">{t("update")}</Button>
</div>
</Form>
</div>
</DialogContent>
</Dialog>
);
};
@@ -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";
@@ -77,7 +77,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
/>
<DialogFooter>
<DialogClose>
<DialogClose asChild>
<Button color="secondary">{t("cancel")}</Button>
</DialogClose>
<Button
@@ -56,23 +56,32 @@ export default function CreateEventTypeButton(props: Props) {
: undefined;
const pageSlug = router.query.eventPage || props.options[0].slug;
const hasTeams = !!props.options.find((option) => option.teamId);
const title: string =
typeof router.query.title === "string" && router.query.title ? router.query.title : "";
const length: number =
typeof router.query.length === "string" && router.query.length ? parseInt(router.query.length) : 15;
const description: string =
typeof router.query.description === "string" && router.query.description ? router.query.description : "";
const slug: string = typeof router.query.slug === "string" && router.query.slug ? router.query.slug : "";
const type: string = typeof router.query.type == "string" && router.query.type ? router.query.type : "";
const form = useForm<z.infer<typeof createEventTypeInput>>({
resolver: zodResolver(createEventTypeInput),
});
const { setValue, watch, register } = form;
setValue("title", title);
setValue("length", length);
setValue("description", description);
setValue("slug", slug);
useEffect(() => {
if (!router.isReady) return;
const title: string =
typeof router.query.title === "string" && router.query.title ? router.query.title : "";
const length: number =
typeof router.query.length === "string" && router.query.length ? parseInt(router.query.length) : 15;
const description: string =
typeof router.query.description === "string" && router.query.description
? router.query.description
: "";
const slug: string = typeof router.query.slug === "string" && router.query.slug ? router.query.slug : "";
setValue("title", title);
setValue("length", length);
setValue("description", description);
setValue("slug", slug);
// If query params change, update the form
}, [router.isReady, router.query, setValue]);
useEffect(() => {
const subscription = watch((value, { name, type }) => {
@@ -86,7 +95,7 @@ export default function CreateEventTypeButton(props: Props) {
const createMutation = trpc.useMutation("viewer.eventTypes.create", {
onSuccess: async ({ eventType }) => {
await router.push("/event-types/" + eventType.id);
await router.replace("/event-types/" + eventType.id);
showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success");
},
onError: (err) => {
@@ -95,6 +104,11 @@ export default function CreateEventTypeButton(props: Props) {
showToast(message, "error");
}
if (err.data?.code === "BAD_REQUEST") {
const message = `${err.data.code}: URL already exists.`;
showToast(message, "error");
}
if (err.data?.code === "UNAUTHORIZED") {
const message = `${err.data.code}: You are not able to create this event`;
showToast(message, "error");
@@ -1,11 +1,13 @@
import { ClockIcon, CreditCardIcon, UserIcon, UsersIcon } from "@heroicons/react/solid";
import { ClockIcon, CreditCardIcon, RefreshIcon, UserIcon, UsersIcon } from "@heroicons/react/solid";
import { SchedulingType } from "@prisma/client";
import { Prisma } from "@prisma/client";
import React from "react";
import React, { useMemo } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RecurringEvent } from "@calcom/types/Calendar";
import classNames from "@lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
const eventTypeData = Prisma.validator<Prisma.EventTypeArgs>()({
select: {
@@ -14,6 +16,7 @@ const eventTypeData = Prisma.validator<Prisma.EventTypeArgs>()({
price: true,
currency: true,
schedulingType: true,
recurringEvent: true,
description: true,
},
});
@@ -28,6 +31,11 @@ export type EventTypeDescriptionProps = {
export const EventTypeDescription = ({ eventType, className }: EventTypeDescriptionProps) => {
const { t } = useLocale();
const recurringEvent: RecurringEvent = useMemo(
() => (eventType.recurringEvent as RecurringEvent) || [],
[eventType.recurringEvent]
);
return (
<>
<div className={classNames("text-neutral-500 dark:text-white", className)}>
@@ -54,6 +62,12 @@ export const EventTypeDescription = ({ eventType, className }: EventTypeDescript
{t("1_on_1")}
</li>
)}
{recurringEvent?.count && recurringEvent.count > 0 && (
<li className="flex whitespace-nowrap">
<RefreshIcon className="mt-0.5 mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
{t("repeats_up_to", { count: recurringEvent.count })}
</li>
)}
{eventType.price > 0 && (
<li className="flex whitespace-nowrap">
<CreditCardIcon className="mt-0.5 mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
@@ -0,0 +1,149 @@
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";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RecurringEvent } from "@calcom/types/Calendar";
import { Alert } from "@calcom/ui/Alert";
import Select from "@components/ui/form/Select";
type RecurringEventControllerProps = {
recurringEvent: RecurringEvent;
formMethods: UseFormReturn<FormValues>;
paymentEnabled: boolean;
onRecurringEventDefined: (value: boolean) => void;
};
export default function RecurringEventController({
recurringEvent,
formMethods,
paymentEnabled,
onRecurringEventDefined,
}: RecurringEventControllerProps) {
const { t } = useLocale();
const [recurringEventDefined, setRecurringEventDefined] = useState(recurringEvent?.count !== undefined);
const [recurringEventInterval, setRecurringEventInterval] = useState(recurringEvent?.interval || 1);
const [recurringEventFrequency, setRecurringEventFrequency] = useState(
recurringEvent?.freq || RRuleFrequency.WEEKLY
);
const [recurringEventCount, setRecurringEventCount] = useState(recurringEvent?.count || 12);
/* Just yearly-0, monthly-1 and weekly-2 */
const recurringEventFreqOptions = Object.entries(RRuleFrequency)
.filter(([key, value]) => isNaN(Number(key)) && Number(value) < 3)
.map(([key, value]) => ({
label: t(`${key.toString().toLowerCase()}`, { count: recurringEventInterval }),
value: value.toString(),
}));
return (
<div className="block items-start sm:flex">
<div className="min-w-48 mb-4 sm:mb-0">
<span className="flex text-sm font-medium text-neutral-700">{t("recurring_event")}</span>
</div>
<div className={!paymentEnabled ? "w-full" : ""}>
{paymentEnabled ? (
<Alert severity="warning" title={t("warning_payment_recurring_event")} />
) : (
<>
<div className="relative flex items-start">
<div className="flex h-5 items-center">
<input
onChange={(event) => {
setRecurringEventDefined(event?.target.checked);
onRecurringEventDefined(event?.target.checked);
if (!event?.target.checked) {
formMethods.setValue("recurringEvent", {});
} else {
formMethods.setValue(
"recurringEvent",
recurringEventDefined
? recurringEvent
: {
interval: 1,
count: 12,
freq: RRuleFrequency.WEEKLY,
}
);
}
recurringEvent = formMethods.getValues("recurringEvent");
}}
type="checkbox"
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">
<label htmlFor="recurringEvent" className="text-neutral-900">
{t("recurring_event_description")}
</label>
</div>
</div>
<Collapsible
open={recurringEventDefined}
data-testid="recurring-event-collapsible"
onOpenChange={() => setRecurringEventDefined(!recurringEventDefined)}>
<CollapsibleContent className="mt-4 text-sm">
<div className="flex items-center">
<p className="mr-2 text-neutral-900">{t("repeats_every")}</p>
<input
type="number"
min="1"
max="20"
className="block w-16 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
defaultValue={recurringEvent?.interval || 1}
onChange={(event) => {
setRecurringEventInterval(parseInt(event?.target.value));
recurringEvent.interval = parseInt(event?.target.value);
formMethods.setValue("recurringEvent", recurringEvent);
}}
/>
<Select
options={recurringEventFreqOptions}
value={recurringEventFreqOptions[recurringEventFrequency]}
isSearchable={false}
className="w-18 block min-w-0 rounded-sm sm:text-sm"
onChange={(e) => {
if (e?.value) {
setRecurringEventFrequency(parseInt(e?.value));
recurringEvent.freq = parseInt(e?.value);
formMethods.setValue("recurringEvent", recurringEvent);
}
}}
/>
</div>
<div className="mt-4 flex items-center">
<p className="mr-2 text-neutral-900">{t("max")}</p>
<input
type="number"
min="1"
max="20"
className="block w-16 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
defaultValue={recurringEvent?.count || 12}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
recurringEvent.count = parseInt(event?.target.value);
formMethods.setValue("recurringEvent", recurringEvent);
}}
/>
<p className="mr-2 text-neutral-900">
{t(`${RRuleFrequency[recurringEventFrequency].toString().toLowerCase()}`, {
count: recurringEventCount,
})}
</p>
</div>
</CollapsibleContent>
</Collapsible>
</>
)}
</div>
</div>
);
}
@@ -0,0 +1,63 @@
import { LinkIcon } from "@heroicons/react/outline";
import { ClockIcon, DotsHorizontalIcon, ExternalLinkIcon, UserIcon } from "@heroicons/react/solid";
import React from "react";
import { SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
function SkeletonLoader() {
return (
<SkeletonContainer>
<div className="mb-4 flex items-center">
<SkeletonAvatar width="8" height="8"></SkeletonAvatar>
<div className="space-y-1">
<SkeletonText height="4" width="16"></SkeletonText>
<SkeletonText height="4" width="24"></SkeletonText>
</div>
</div>
<ul className="divide-y divide-neutral-200 border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem />
<SkeletonItem />
<SkeletonItem />
</ul>
</SkeletonContainer>
);
}
export default SkeletonLoader;
function SkeletonItem() {
return (
<li className="group flex w-full items-center justify-between px-4 py-4 sm:px-6">
<div className="flex-grow truncate text-sm">
<div>
<SkeletonText width="32" height="5"></SkeletonText>
</div>
<div className="">
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
<li className="flex items-center whitespace-nowrap">
<ClockIcon className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200"></ClockIcon>
<SkeletonText width="12" height="4"></SkeletonText>
</li>
<li className="flex items-center whitespace-nowrap">
<UserIcon className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200"></UserIcon>
<SkeletonText width="16" height="4"></SkeletonText>
</li>
</ul>
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 sm:flex">
<div className="flex justify-between rtl:space-x-reverse">
<div className="btn-icon appearance-none">
<ExternalLinkIcon className="h-5 w-5" />
</div>
<div className="btn-icon appearance-none">
<LinkIcon className="h-5 w-5" />
</div>
<div className="btn-icon appearance-none">
<DotsHorizontalIcon className="h-5 w-5" />
</div>
</div>
</div>
</li>
);
}
@@ -139,7 +139,7 @@ function ConnectedCalendarsList(props: Props) {
) : (
<Alert
severity="warning"
title="Something went wrong"
title={t("calendar_error")}
message={item.error?.message}
actions={
<DisconnectIntegration
@@ -0,0 +1,69 @@
import { useState } from "react";
import { useMutation } from "react-query";
import showToast from "@calcom/lib/notification";
import { ButtonBaseProps } from "@calcom/ui/Button";
import { Dialog } from "@calcom/ui/Dialog";
import DeleteStripeDialogContent from "@components/dialog/DeleteStripeDialogContent";
export default function DisconnectStripeIntegration(props: {
/** Integration credential id */
id: number;
render: (renderProps: ButtonBaseProps) => JSX.Element;
onOpenChange: (isOpen: boolean) => unknown | Promise<unknown>;
}) {
const [modalOpen, setModalOpen] = useState(false);
const mutation = useMutation(
async (action: string) => {
const res = await fetch("/api/integrations", {
method: "DELETE",
body: JSON.stringify({ id: props.id, action }),
headers: {
"Content-Type": "application/json",
},
});
if (!res.ok) {
throw new Error("Something went wrong");
}
return res.json();
},
{
async onSettled() {
await props.onOpenChange(modalOpen);
},
onSuccess(data) {
showToast(data.message, "success");
setModalOpen(false);
},
}
);
return (
<>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DeleteStripeDialogContent
variety="warning"
title="Disconnect Stripe Integration"
cancelAllBookingsBtnText="Cancel all bookings"
removeBtnText="Remove payment"
cancelBtnText="Cancel"
onConfirm={() => {
mutation.mutate("cancel");
}}
onRemove={() => {
mutation.mutate("remove");
}}>
If you have unpaid and unconfirmed bookings, you must choose to cancel them or remove the required
payment field.
</DeleteStripeDialogContent>
</Dialog>
{props.render({
onClick() {
setModalOpen(true);
},
disabled: modalOpen,
loading: mutation.isLoading,
})}
</>
);
}
@@ -14,7 +14,10 @@ function IntegrationListItem(props: {
return (
<ListItem expanded={!!props.children} className={classNames("flex-col")}>
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-3 rtl:space-x-reverse")}>
{props.imageSrc && <img className="h-10 w-10" src={props.imageSrc} alt={props.title} />}
{
// eslint-disable-next-line @next/next/no-img-element
props.imageSrc && <img className="h-10 w-10" src={props.imageSrc} alt={props.title} />
}
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3">{props.title}</ListItemTitle>
<ListItemText component="p">{props.description}</ListItemText>
@@ -56,13 +56,13 @@ const ChangePasswordSection = () => {
return (
<>
<div className="mt-6">
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">{t("change_password")}</h2>
</div>
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={changePasswordHandler}>
<div className="py-6 lg:pb-8">
<div className="flex">
<div className="w-1/2 ltr:mr-2 rtl:ml-2">
<div className="py-6 lg:pb-5">
<div className="my-3">
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">{t("change_password")}</h2>
</div>
<div className="flex flex-col space-y-2 sm:flex-row sm:space-y-0">
<div className="w-full ltr:mr-2 rtl:ml-2 sm:w-1/2">
<label htmlFor="current_password" className="block text-sm font-medium text-gray-700">
{t("current_password")}
</label>
@@ -79,7 +79,7 @@ const ChangePasswordSection = () => {
/>
</div>
</div>
<div className="ml-2 w-1/2">
<div className="w-full sm:w-1/2">
<label htmlFor="new_password" className="block text-sm font-medium text-gray-700">
{t("new_password")}
</label>
@@ -98,10 +98,11 @@ const ChangePasswordSection = () => {
</div>
</div>
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
<div className="flex justify-end py-8">
<Button type="submit">{t("save")}</Button>
<div className="flex py-8 sm:justify-end">
<Button color="secondary" type="submit">
{t("save")}
</Button>
</div>
<hr className="mt-4" />
</div>
</form>
</>
@@ -0,0 +1,55 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import Button from "@calcom/ui/Button";
import { trpc } from "@lib/trpc";
import Badge from "@components/ui/Badge";
const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonation: boolean }) => {
const utils = trpc.useContext();
const { t } = useLocale();
const mutation = trpc.useMutation("viewer.updateProfile", {
onSuccess: async () => {
showToast(t("your_user_profile_updated_successfully"), "success");
await utils.invalidateQueries(["viewer.me"]);
},
async onSettled() {
await utils.invalidateQueries(["viewer.i18n"]);
},
});
return (
<>
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
<div>
<div className="flex flex-row items-center">
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">
{t("user_impersonation_heading")}
</h2>
<Badge className="ml-2 text-xs" variant={!disableImpersonation ? "success" : "gray"}>
{!disableImpersonation ? t("enabled") : t("disabled")}
</Badge>
</div>
<p className="mt-1 text-sm text-gray-500">{t("user_impersonation_description")}</p>
</div>
<div className="mt-5 sm:mt-0 sm:self-center">
<Button
type="submit"
color="secondary"
onClick={() =>
!disableImpersonation
? mutation.mutate({ disableImpersonation: true })
: mutation.mutate({ disableImpersonation: false })
}>
{!disableImpersonation ? t("disable") : t("enable")}
</Button>
</div>
</div>
</>
);
};
export default DisableUserImpersonation;
@@ -150,7 +150,10 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
<WithStep step={SetupStep.DisplayQrCode} current={step}>
<>
<div className="flex justify-center">
<img src={dataUri} />
{
// eslint-disable-next-line @next/next/no-img-element
<img src={dataUri} alt="" />
}
</div>
<p className="text-center font-mono text-xs">{secret}</p>
</>
@@ -173,6 +176,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
inputMode="numeric"
onInput={(e) => setTotpCode(e.currentTarget.value)}
className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
autoComplete="one-time-code"
/>
</div>
@@ -17,21 +17,25 @@ const TwoFactorAuthSection = ({ twoFactorEnabled }: { twoFactorEnabled: boolean
return (
<>
<div className="flex flex-row items-center">
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">{t("2fa")}</h2>
<Badge className="ml-2 text-xs" variant={enabled ? "success" : "gray"}>
{enabled ? t("enabled") : t("disabled")}
</Badge>
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
<div>
<div className="flex flex-row items-center">
<h2 className="font-cal text-lg font-medium leading-6 text-gray-900">{t("2fa")}</h2>
<Badge className="ml-2 text-xs" variant={enabled ? "success" : "gray"}>
{enabled ? t("enabled") : t("disabled")}
</Badge>
</div>
<p className="mt-1 text-sm text-gray-500">{t("add_an_extra_layer_of_security")}</p>
</div>
<div className="mt-5 sm:mt-0 sm:self-center">
<Button
type="submit"
color="secondary"
onClick={() => (enabled ? setDisableModalOpen(true) : setEnableModalOpen(true))}>
{enabled ? t("disable") : t("enable")}
</Button>
</div>
</div>
<p className="mt-1 text-sm text-gray-500">{t("add_an_extra_layer_of_security")}</p>
<Button
className="mt-6"
type="submit"
onClick={() => (enabled ? setDisableModalOpen(true) : setEnableModalOpen(true))}>
{enabled ? t("disable") : t("enable")} {t("2fa")}
</Button>
{enableModalOpen && (
<EnableTwoFactorModal
onEnable={() => {
@@ -15,10 +15,11 @@ type MembershipRoleOption = {
label?: string;
};
const options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }];
const options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }, { value: "OWNER" }];
export default function MemberChangeRoleModal(props: {
isOpen: boolean;
currentMember: MembershipRole;
memberId: number;
teamId: number;
initialRole: MembershipRole;
@@ -57,7 +58,6 @@ export default function MemberChangeRoleModal(props: {
role: role.value,
});
}
return (
<ModalContainer isOpen={props.isOpen} onExit={props.onExit}>
<>
@@ -76,7 +76,7 @@ export default function MemberChangeRoleModal(props: {
{/*<option value="OWNER">{t("owner")}</option> - needs dialog to confirm change of ownership */}
<Select
isSearchable={false}
options={options}
options={props.currentMember !== MembershipRole.OWNER ? options.slice(0, 2) : options}
value={role}
onChange={(option) => option && setRole(option)}
id="role"
@@ -1,10 +1,10 @@
import { UserIcon } from "@heroicons/react/outline";
import { InformationCircleIcon } from "@heroicons/react/solid";
import { MembershipRole } from "@prisma/client";
import React, { useState, useEffect, SyntheticEvent, useMemo } from "react";
import React, { useState, SyntheticEvent, useMemo } from "react";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Dialog, DialogContent, DialogFooter } from "@calcom/ui/Dialog";
import { TextField } from "@calcom/ui/form/fields";
import { useLocale } from "@lib/hooks/useLocale";
@@ -16,6 +16,7 @@ import Select from "@components/ui/form/Select";
type MemberInvitationModalProps = {
isOpen: boolean;
team: TeamWithMembers | null;
currentMember: MembershipRole;
onExit: () => void;
};
@@ -24,7 +25,7 @@ type MembershipRoleOption = {
label?: string;
};
const _options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }];
const _options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }, { value: "OWNER" }];
export default function MemberInvitationModal(props: MemberInvitationModalProps) {
const [errorMessage, setErrorMessage] = useState("");
@@ -100,7 +101,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
</label>
<Select
defaultValue={options[0]}
options={options}
options={props.currentMember !== MembershipRole.OWNER ? options.slice(0, 2) : options}
id="role"
name="role"
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
+24 -10
View File
@@ -1,9 +1,10 @@
import { UserRemoveIcon, PencilIcon } from "@heroicons/react/outline";
import { ClockIcon, ExternalLinkIcon, DotsHorizontalIcon } from "@heroicons/react/solid";
import { PencilIcon, UserRemoveIcon } from "@heroicons/react/outline";
import { ClockIcon, DotsHorizontalIcon, ExternalLinkIcon } from "@heroicons/react/solid";
import { MembershipRole } from "@prisma/client";
import Link from "next/link";
import React, { useState } from "react";
import { useState } from "react";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import Button from "@calcom/ui/Button";
@@ -14,12 +15,12 @@ import Dropdown, {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@calcom/ui/Dropdown";
import { Tooltip } from "@calcom/ui/Tooltip";
import TeamAvailabilityModal from "@ee/components/team/availability/TeamAvailabilityModal";
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
import { trpc, inferQueryOutput } from "@lib/trpc";
import useCurrentUserId from "@lib/hooks/useCurrentUserId";
import { inferQueryOutput, trpc } from "@lib/trpc";
import { Tooltip } from "@components/Tooltip";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
import Avatar from "@components/ui/Avatar";
import ModalContainer from "@components/ui/ModalContainer";
@@ -49,6 +50,14 @@ export default function MemberListItem(props: Props) {
},
});
const ownersInTeam = () => {
const { members } = props.team;
const owners = members.filter((member) => member["role"] === MembershipRole.OWNER && member["accepted"]);
return owners.length;
};
const currentUserId = useCurrentUserId();
const name =
props.member.name ||
(() => {
@@ -65,7 +74,7 @@ export default function MemberListItem(props: Props) {
<div className="flex w-full flex-col justify-between sm:flex-row">
<div className="flex">
<Avatar
imageSrc={getPlaceholderAvatar(props.member?.avatar, name)}
imageSrc={WEBAPP_URL + "/" + props.member.username + "/avatar.png"}
alt={name || ""}
className="h-9 w-9 rounded-full"
/>
@@ -121,8 +130,12 @@ export default function MemberListItem(props: Props) {
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" />
{(props.team.membership.role === MembershipRole.OWNER ||
props.team.membership.role === MembershipRole.ADMIN) && (
{((props.team.membership.role === MembershipRole.OWNER &&
(props.member.role !== MembershipRole.OWNER ||
ownersInTeam() > 1 ||
props.member.id !== currentUserId)) ||
(props.team.membership.role === MembershipRole.ADMIN &&
props.member.role !== MembershipRole.OWNER)) && (
<>
<DropdownMenuItem>
<Button
@@ -165,6 +178,7 @@ export default function MemberListItem(props: Props) {
{showChangeMemberRoleModal && (
<MemberChangeRoleModal
isOpen={showChangeMemberRoleModal}
currentMember={props.team.membership.role}
teamId={props.team?.id}
memberId={props.member.id}
initialRole={props.member.role as MembershipRole}
@@ -181,7 +195,7 @@ export default function MemberListItem(props: Props) {
<div className="space-x-2 border-t py-5 rtl:space-x-reverse">
<Button onClick={() => setShowTeamAvailabilityModal(false)}>{t("done")}</Button>
{props.team.membership.role !== MembershipRole.MEMBER && (
<Link href={`/settings/teams/${props.team.id}/availability`}>
<Link href={`/settings/teams/${props.team.id}/availability`} passHref>
<Button color="secondary">{t("Open Team Availability")}</Button>
</Link>
)}
+4 -2
View File
@@ -1,6 +1,6 @@
import showToast from "@calcom/lib/notification";
import { trpc, inferQueryOutput } from "@lib/trpc";
import { inferQueryOutput, trpc } from "@lib/trpc";
import TeamListItem from "./TeamListItem";
@@ -39,7 +39,9 @@ export default function TeamList(props: Props) {
<TeamListItem
key={team?.id as number}
team={team}
onActionSelect={(action: string) => selectAction(action, team?.id as number)}></TeamListItem>
onActionSelect={(action: string) => selectAction(action, team?.id as number)}
isLoading={deleteTeamMutation.isLoading}
/>
))}
</ul>
</div>
+11 -7
View File
@@ -1,10 +1,10 @@
import { LogoutIcon } from "@heroicons/react/outline";
import {
ExternalLinkIcon,
TrashIcon,
LinkIcon,
DotsHorizontalIcon,
ExternalLinkIcon,
LinkIcon,
PencilIcon,
TrashIcon,
} from "@heroicons/react/solid";
import { MembershipRole } from "@prisma/client";
import Link from "next/link";
@@ -16,15 +16,15 @@ import { Dialog, DialogTrigger } from "@calcom/ui/Dialog";
import Dropdown, {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@calcom/ui/Dropdown";
import { Tooltip } from "@calcom/ui/Tooltip";
import classNames from "@lib/classNames";
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
import { trpc, inferQueryOutput } from "@lib/trpc";
import { inferQueryOutput, trpc } from "@lib/trpc";
import { Tooltip } from "@components/Tooltip";
import ConfirmationDialogContent from "@components/dialog/ConfirmationDialogContent";
import Avatar from "@components/ui/Avatar";
@@ -34,6 +34,7 @@ interface Props {
team: inferQueryOutput<"viewer.teams.list">[number];
key: number;
onActionSelect: (text: string) => void;
isLoading?: boolean;
}
export default function TeamListItem(props: Props) {
@@ -175,7 +176,10 @@ export default function TeamListItem(props: Props) {
variety="danger"
title={t("disband_team")}
confirmBtnText={t("confirm_disband_team")}
onConfirm={() => props.onActionSelect("disband")}>
isLoading={props.isLoading}
onConfirm={() => {
props.onActionSelect("disband");
}}>
{t("disband_team_confirmation_message")}
</ConfirmationDialogContent>
</Dialog>
+6 -7
View File
@@ -5,13 +5,12 @@ import Link from "next/link";
import { TeamPageProps } from "pages/team/[slug]";
import React from "react";
import { WEBAPP_URL } from "@calcom/lib/constants";
import Button from "@calcom/ui/Button";
import { getPlaceholderAvatar } from "@lib/getPlaceholderAvatar";
import { useLocale } from "@lib/hooks/useLocale";
import Avatar from "@components/ui/Avatar";
import Text from "@components/ui/Text";
type TeamType = TeamPageProps["team"];
type MembersType = TeamType["members"];
@@ -52,14 +51,14 @@ const Team = ({ team }: TeamPageProps) => {
<div>
<Avatar
alt={member.name || ""}
imageSrc={getPlaceholderAvatar(member.avatar, member.username)}
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
className="-mt-4 h-12 w-12"
/>
<section className="mt-2 w-full space-y-1">
<Text variant="title">{member.name}</Text>
<Text variant="subtitle" className="">
<section className="line-clamp-4 mt-2 w-full space-y-1">
<p className="font-medium text-neutral-900 dark:text-white">{member.name}</p>
<p className="text-sm font-normal text-neutral-500 dark:text-white">
{member.bio || t("user_from_team", { user: member.name, team: team.name })}
</Text>
</p>
</section>
</div>
</div>
+15
View File
@@ -0,0 +1,15 @@
import { useSession } from "next-auth/react";
import { FC, Fragment } from "react";
type AdminRequiredProps = {
as?: keyof JSX.IntrinsicElements;
children?: React.ReactNode;
};
export const AdminRequired: FC<AdminRequiredProps> = ({ children, as, ...rest }) => {
const session = useSession();
if (session.data?.user.role !== "ADMIN") return null;
const Component = as ?? Fragment;
return <Component {...rest}>{children}</Component>;
};
+3 -1
View File
@@ -1,3 +1,4 @@
import classNames from "classnames";
import React from "react";
import Loader from "@components/Loader";
@@ -16,8 +17,9 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
return (
<div className="flex min-h-screen flex-col justify-center bg-neutral-50 py-12 sm:px-6 lg:px-8">
<HeadSeo title={props.title} description={props.description} />
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
{props.showLogo && (
// eslint-disable-next-line @next/next/no-img-element
<img className="mx-auto h-6" src="/calendso-logo-white-word.svg" alt="Cal.com Logo" />
)}
{props.heading && (
+1
View File
@@ -27,6 +27,7 @@ export default function Avatar(props: AvatarProps) {
/>
<AvatarPrimitive.Fallback delayMs={600}>
{gravatarFallbackMd5 && (
// eslint-disable-next-line @next/next/no-img-element
<img src={defaultAvatarSrc({ md5: gravatarFallbackMd5 })} alt={alt} className={className} />
)}
</AvatarPrimitive.Fallback>
+1
View File
@@ -28,5 +28,6 @@ export function AvatarSSR(props: AvatarProps) {
} else if (user.emailMd5) {
imgSrc = defaultAvatarSrc(user.emailMd5);
}
// eslint-disable-next-line @next/next/no-img-element
return imgSrc ? <img alt={alt} className={className} src={imgSrc}></img> : null;
}
@@ -0,0 +1,32 @@
import { useSession } from "next-auth/react";
import { Trans } from "next-i18next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Alert } from "@calcom/ui/Alert";
function ImpersonatingBanner() {
const { t } = useLocale();
const { data } = useSession();
if (!data?.user.impersonatedByUID) return null;
return (
<Alert
severity="warning"
title={
<>
{t("impersonating_user_warning", { user: data.user.username })}{" "}
<Trans i18nKey="impersonating_stop_instructions">
<a href="/auth/logout" className="underline">
Click Here To stop
</a>
.
</Trans>
</>
}
className="mx-4 mb-2 sm:mx-6 md:mx-8"
/>
);
}
export default ImpersonatingBanner;
+1 -1
View File
@@ -1,6 +1,6 @@
import { InformationCircleIcon } from "@heroicons/react/solid";
import { Tooltip } from "@components/Tooltip";
import { Tooltip } from "@calcom/ui/Tooltip";
export default function InfoBadge({ content }: { content: string }) {
return (
+3 -3
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;
@@ -18,11 +18,11 @@ export default function ModalContainer(props: Props) {
<DialogContent>
<div
className={classNames(
"inline-block transform bg-white text-left align-bottom transition-all sm:align-middle",
"inline-block w-full transform bg-white text-left align-bottom transition-all sm:align-middle",
{
"sm:w-full sm:max-w-lg ": !props.wide,
"sm:w-4xl sm:max-w-4xl": props.wide,
"overflow-scroll": props.scroll,
"overflow-auto": props.scroll,
"!p-0": props.noPadding,
}
)}>
+17 -11
View File
@@ -1,6 +1,6 @@
import Link from "next/link";
import { useIsEmbed } from "@calcom/embed-core";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { useLocale } from "@lib/hooks/useLocale";
@@ -12,16 +12,22 @@ const PoweredByCal = () => {
<Link href={`https://cal.com?utm_source=embed&utm_medium=powered-by-button`}>
<a target="_blank" className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
{t("powered_by")}{" "}
<img
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="https://cal.com/logo.svg"
alt="Cal.com Logo"
/>
<img
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
src="https://cal.com/logo-white.svg"
alt="Cal.com Logo"
/>
{
// eslint-disable-next-line @next/next/no-img-element
<img
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="/cal-logo-word.svg"
alt="Cal.com Logo"
/>
}
{
// eslint-disable-next-line @next/next/no-img-element
<img
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
src="/cal-logo-word-dark.svg"
alt="Cal.com Logo"
/>
}
</a>
</Link>
</div>
+29 -9
View File
@@ -2,25 +2,38 @@ 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";
export type ActionType = {
id: string;
icon?: SVGComponent;
iconClassName?: string;
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 +53,8 @@ const DropdownActions = ({ actions, actionTrigger }: { actions: ActionType[]; ac
className="w-full rounded-none font-normal"
href={action.href}
StartIcon={action.icon}
onClick={action.onClick}
startIconClassName={action.iconClassName}
onClick={action.onClick || defaultAction}
data-testid={action.id}>
{action.label}
</Button>
@@ -52,6 +66,12 @@ const DropdownActions = ({ actions, actionTrigger }: { actions: ActionType[]; ac
};
const TableActions: FC<Props> = ({ actions }) => {
const mobileActions = actions.flatMap((action) => {
if (action.actions) {
return action.actions;
}
return action;
});
return (
<>
<div className="hidden space-x-2 rtl:space-x-reverse lg:block">
@@ -61,8 +81,9 @@ 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}
startIconClassName={action.iconClassName}
{...(action?.actions ? { EndIcon: ChevronDownIcon } : null)}
disabled={action.disabled}
color={action.color || "secondary"}>
@@ -72,12 +93,11 @@ const TableActions: FC<Props> = ({ actions }) => {
if (!action.actions) {
return button;
}
return <DropdownActions key={action.id} actions={action.actions} actionTrigger={button} />;
})}
</div>
<div className="inline-block text-left lg:hidden">
<DropdownActions actions={actions} />
<DropdownActions actions={mobileActions} />
</div>
</>
);
-12
View File
@@ -1,12 +0,0 @@
import classnames from "classnames";
import React from "react";
import { TextProps } from "../Text";
const Body: React.FunctionComponent<TextProps> = (props: TextProps) => {
const classes = classnames("text-gray-900 dark:text-white", props?.className);
return <p className={classes}>{props?.text || props.children}</p>;
};
export default Body;

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