Compare commits

...
105 Commits
Author SHA1 Message Date
Alex van Andel 0e7e1c2cc0 v2.8.9
release.yml / release (push) Canceled after 0s
2023-04-22 21:56:46 +02:00
Crowdin Bot eb97feb173 New Crowdin translations by Github Action 2023-04-22 19:53:44 +00:00
Peer Richelsen 7d702dd546 only show vibrancy on MacOS (#8462) 2023-04-22 20:52:50 +01:00
Hariom Balhara 82b6b83885 Possible fix for iOS redirection (#8455) 2023-04-22 19:45:22 +02:00
Peer Richelsen 39b6def444 fixed timezone dropdown (#8461) 2023-04-22 10:32:06 -07:00
Crowdin Bot 7f5cdbab9b New Crowdin translations by Github Action 2023-04-22 15:48:07 +00:00
Peer Richelsen 1770b832f4 fixed bg for profile on light mode (#8457) 2023-04-22 17:47:15 +02:00
Crowdin Bot d30c0a626d New Crowdin translations by Github Action 2023-04-22 10:58:56 +00:00
Syed Ali ShahbazandAlex van Andel 2eed750ef6 Hotfix - CalDAV/Wraps timezone addition in try catch (#8453)
* Wraps timezone addition in try catch

* Suggestion accepted

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

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-04-22 10:58:03 +00:00
dd8968d0e7 Powered by for ee (#8434)
* moved poweredBy into ee/ to make it possible to change it when a whitelabel is applied

* added text-emphasis for custom powered by text in PoweredBy

---------

Co-authored-by: René Müller <rene.mueller@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-04-22 01:57:53 +02:00
Crowdin Bot 827bebbc31 New Crowdin translations by Github Action 2023-04-21 22:44:24 +00:00
Joe Au-Yeung d97ac0e0f8 Fix booking page when toggling payment app settings (#8451)
* Use getPaymentAppData on booking list item

* Use paymentAppdata for event type description

* Remove console.log

* Type fix

* Type fixes

* Remove updating event type price
2023-04-21 15:43:31 -07:00
Crowdin Bot 600d2725aa New Crowdin translations by Github Action 2023-04-21 22:19:49 +00:00
zomars 2a09dc6a46 Comments out e2e page pause 2023-04-21 15:18:45 -07:00
Crowdin Bot 1f746b67f4 New Crowdin translations by Github Action 2023-04-21 22:00:44 +00:00
Omar López bb95f2ba39 Enable prisma json protocol (#7993)
* Enable prisma json protocol

* Upgrades prisma

* Upgrades prisma
2023-04-21 18:59:42 -03:00
Alex van Andel 3d93b4127c Use avatar.png instead of base64. (#8450)
* Use avatar.png instead of base64.

* Removed another case of base64 avatar use
2023-04-21 20:00:31 +02:00
Efraín RochínandKeith Williams 094fae38fc Add log messages on cold/hot requests (#8420)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-04-21 12:08:59 -03:00
Alex van Andel 3af614f778 v2.8.8
release.yml / release (push) Canceled after 0s
2023-04-21 16:21:23 +02:00
Nafees NazikandAlex van Andel 77b41a4623 feat: add support endpoint (#8404)
* feat: open intercom

* Remove redirect from middleware, use next.config.js instead

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-04-21 14:18:57 +00:00
Crowdin Bot c23738b885 New Crowdin translations by Github Action 2023-04-21 13:50:42 +00:00
alannncandAlex van Andel afe1f5b72f Fix/seats-cancel-links (#8394)
* Fix cancel links

* Use searchParams API to build cancelLink

* Fix cancel showSeats

* Added test for owner cancel attendee list

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-04-21 15:49:53 +02:00
Crowdin Bot 5ce341093b New Crowdin translations by Github Action 2023-04-21 11:48:02 +00:00
Crowdin Bot fc6d2839fb New Crowdin translations by Github Action 2023-04-21 11:47:08 +00:00
2fadba85f4 Fix workflow not firing for event types without video app as location (#8435)
* fix error when videoCallUrl was null

* make sure videocallurl is not null

* Revert "fix error when videoCallUrl was null"

This reverts commit f23776ec8be1be334245b7e3f8a977f7e73591f1.

* Update packages/features/bookings/lib/handleNewBooking.ts

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

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-04-21 11:46:23 +00:00
sean-brydon 473b36a6be Bulk update modal (#8433)
* Use checkbox component

* Update packages/features/eventtypes/components/BulkEditDefaultConferencingModal.tsx
2023-04-21 11:04:27 +00:00
Crowdin Bot f74b9eb70a New Crowdin translations by Github Action 2023-04-21 10:51:27 +00:00
Hariom Balhara a845841ad0 Prevent looping b/w dark and light (#8440) 2023-04-21 16:20:27 +05:30
Crowdin Bot dba80a1b64 New Crowdin translations by Github Action 2023-04-21 08:25:54 +00:00
sean-brydon 41558d297c Brand colour on setup tab (#8430) 2023-04-21 10:24:59 +02:00
Crowdin Bot cd3946d482 New Crowdin translations by Github Action 2023-04-21 00:46:08 +00:00
Alex van Andel dcbf695217 Remove getDirFromLang, in favour of doing this in the I18nLanguageHan… (#8425)
* Remove getDirFromLang, in favour of doing this in the I18nLanguageHandler exclusively

* Small tweak
2023-04-21 00:45:22 +00:00
Alex van Andel 49cbdda4cf Use modularizeImports for @heroicons (#8422) 2023-04-21 00:58:57 +02:00
Crowdin Bot 22f458cb74 New Crowdin translations by Github Action 2023-04-20 22:34:42 +00:00
Peer Richelsen 561d9f154b misc frontend fixes (#8416)
* (temp) dark mode changes (#8415)

* fixes skeletons, some wrong buttons, spacing, settings
2023-04-20 22:33:49 +00:00
Peer Richelsen 0c9b9bc296 Update package.json
release.yml / release (push) Canceled after 0s
2023-04-21 00:11:59 +02:00
Alex van Andel 552bfcc7db Moved forgot? behind the label (#8421) 2023-04-20 22:06:35 +00:00
986f9dca8d Fix: appKeys handling (#8406)
* Handle appKeys save and read correctly

* Add missing zod file for zohocrm which needs keys

* Show team invites regardless of user plan

* Linting

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2023-04-20 21:14:50 +00:00
Omar López 3af5a7404e Show team invites regardless of user plan (#8417) 2023-04-20 20:56:38 +00:00
Crowdin Bot 10d3c6ecaa New Crowdin translations by Github Action 2023-04-20 20:02:05 +00:00
Hariom BalharaandKeith Williams a0c37ea4b7 Remove css vars handling from PageWrapper. It was removed from _app.tsx earlier (#8401)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-04-20 17:01:21 -03:00
Crowdin Bot 798999ad1a New Crowdin translations by Github Action 2023-04-20 18:54:58 +00:00
Crowdin Bot d40e306baa New Crowdin translations by Github Action 2023-04-20 18:54:12 +00:00
Crowdin Bot e558e61f6b New Crowdin translations by Github Action 2023-04-20 18:53:15 +00:00
Crowdin Bot 210eafcd6e New Crowdin translations by Github Action 2023-04-20 18:52:13 +00:00
Crowdin Bot c05e7d75a5 New Crowdin translations by Github Action 2023-04-20 18:51:26 +00:00
Crowdin Bot d0ebf3319a New Crowdin translations by Github Action 2023-04-20 18:50:44 +00:00
Keith Williams 9eb4187644 Added write permission for pull requests in cron action (#8391) 2023-04-20 11:49:52 -07:00
Crowdin Bot 958cebc82c New Crowdin translations by Github Action 2023-04-20 15:57:11 +00:00
Crowdin Bot 0c36dcac4c New Crowdin translations by Github Action 2023-04-20 15:56:18 +00:00
alannncandAlex van Andel fe996b4e41 Fix/user creds (#8393)
* fix-user-creds

* Type fixes

* Type fixes

* Type fixes

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-04-20 15:55:19 +00:00
Alex van Andel c1c95f711b v2.8.6
release.yml / release (push) Canceled after 0s
2023-04-20 15:54:26 +02:00
Syed Ali Shahbaz 7cb7006c54 CalDAV - improves the UTC offset eval to include DST (#8399)
* improves the UTC offset eval to include DST

* remove debug remnants
2023-04-20 15:51:39 +02:00
Alex van Andel e1403bfbeb Initial implementation, getAppLogos() (#8405)
* Initial implementation, getAppLogos()

* Handle localhost:3000 correctly

* New URL wasn't working with all hostnames

* Only get the first of the subdomainParts, join not needed

* rename url -> hostname

* getAppLogos -> getTeamLogos
2023-04-20 13:43:06 +00:00
Hariom Balhara c38dd2d072 Hotfix: Fix theme not changing once page has started loading (#8400)
* Fix theme not changing once page is loaded

* Add toggle case in playground
2023-04-20 11:57:28 +00:00
Crowdin Bot f9d9e74d6b New Crowdin translations by Github Action 2023-04-20 11:39:48 +00:00
sean-brydon 2ab597addc Fixing default event location URL for dynamic events (#8396)
* Set bookingLocaiton to URL if there is a default location

* Update packages/features/bookings/lib/handleNewBooking.ts

* Suggestions implmented

* Remove old implemntation

* Cleanup
2023-04-20 13:38:57 +02:00
Hariom Balhara 33e394d5ed One App shouldnt break other app, or the entire cal.com build. Also, handle slug change in seeding update (#8294) 2023-04-20 04:41:14 +00:00
Crowdin Bot 4577de3b17 New Crowdin translations by Github Action 2023-04-19 23:03:32 +00:00
Crowdin Bot 658b529840 New Crowdin translations by Github Action 2023-04-19 23:02:40 +00:00
Alex van Andel 4aed769da4 Invert domain check, make default logo work 2023-04-20 01:01:29 +02:00
Crowdin Bot 6c753290aa New Crowdin translations by Github Action
release.yml / release (push) Canceled after 0s
2023-04-19 22:20:17 +00:00
Alex van Andel 59c91607db v2.8.5 2023-04-20 00:19:14 +02:00
b0530d59af Remove Intl.Provider (#8208)
* Add payment option to schema

* Add payment option to Stripe zod

* Set payment option on event type

* Create manual payment intent in Stripe

* Set payment option from Stripe app

* Add payment option to DB

* Pass React.ReactNode to checkbox

* Create uncaptured payment intent

* WIP

* Capture card in setup intent

* Show charge card option

* Charge card from booking page

* Bug fixes

* Clean up

* Clean up app card

* Add no-show fee messaging on booking page

* Send payment email on payment & add price

* Fix messaging

* Create no show fee charged email

* Send charge fee collected email

* Disable submit on card failure

* Clean up

* Serverside prevent charging card again if already charged

* Only confirm booking if paid for

* Type fixes

* More type fixes

* More type fixes

* Type fix

* Type fixes

* UI changes

* Payment component rework

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

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

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

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

* Update apps/web/components/dialog/ChargeCardDialog.tsx

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

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

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

* Revert GTM config

* Adjust payment option dropdown

* Show alert when seats are set

* Small bug fixes

* Create collect card method

* clean up

* Prevent seats & charge no-show fee to be enabled together

* Do not charge no-show fee on unconfirmed bookings

* Add check to collect card method

* Webhook send request emails

* Fix some dark mode colours

* Change awaiting payment language

* Type fixes

* Set height of Select and TextField both to 38px to fix alignment

* Fix message seats & payment error message

* Type fix

* Remove Intl.Provider

* Add percentFeePercentage

* WIP

* Use i18n language

* Type fix

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
2023-04-19 21:38:06 +00:00
Omar López b1ff829745 Warns about lodash usage (#8390) 2023-04-19 23:07:48 +02:00
sean-brydonandzomars d202b536b5 Feat/subdomain logochanges (#8264)
* logo map

* Archimed logo

* usememo

* Subdomain logo logic

* or our logo

* Update packages/features/orgs/SubdomainProvider.tsx

* Provider comments

* Move DB logic to db

* Fix a11y on alt tag

* Added example for dynamic endpoint

* Move to API approach

* Implement Icon logo on subdomain level

---------

Co-authored-by: zomars <zomars@me.com>
2023-04-19 13:55:40 -07:00
Joe Au-Yeung 2ac5ef10ba Add event type redirect URL (#8385) 2023-04-19 13:46:59 -07:00
8a2ab68e9a feat: add user hash for intercom (#8263)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-04-19 22:22:07 +02:00
877220caa0 Event Type: Title not displayed in the mobile view (#7451)
* Event Type: Title not displayed in the mobile view

* Fix toggle not updating in UI

---------

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-04-19 13:17:54 -07:00
0686c08de3 Fix/insights load speed (#8364)
* Adding skipBatch to trpc calls

* Use view in trpc insights

* Added performance data creation on seed-insights

* fix some scenarios

* Fix migration view fields

* Prevent revalidating too often

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2023-04-19 20:14:09 +00:00
Alex van Andel d3f1f8b906 Shaves off an easy 1.4kb win from the global package (#8382) 2023-04-19 16:23:49 -03:00
Crowdin Bot 33a69d3ef6 New Crowdin translations by Github Action 2023-04-19 18:25:28 +00:00
Alex van Andel d91b451d7b Also unpack React / don't directly import React (#8384) 2023-04-19 20:24:34 +02:00
Nafees Nazik 9e17149e5e fix: limit avatar upload size (#8135) 2023-04-19 18:19:31 +02:00
Peer Richelsen de61b032b7 RFC: added brandcolor for switch (#8213) 2023-04-19 14:39:23 +00:00
bdbf9bd04d perf: reduce bundle size (#8337)
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2023-04-19 19:45:08 +05:30
Henrique PachecoandUdit Takkar ccd589ef38 fix: addon wrapper to password input field (#8320)
* fix: addon wrapper to password input field

* fix: tooltip

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-04-19 14:12:55 +00:00
Crowdin Bot b4c3753566 New Crowdin translations by Github Action 2023-04-19 14:03:48 +00:00
2fdadb98ac fix the issue "ReferenceError: window is not defined" (#8193)
Co-authored-by: Nafees Nazik <84864519+G3root@users.noreply.github.com>
Co-authored-by: nafees nazik <nafeesnazik21@gmail.com>
2023-04-19 19:32:55 +05:30
Crowdin Bot a33847a878 New Crowdin translations by Github Action 2023-04-19 13:03:16 +00:00
Nafees Nazik 88cf535c8e fix: address input (#8352) 2023-04-19 13:02:26 +00:00
Carina WollendorferandCarinaWolli ea0b59d7c0 Fixes editor not saving new value (#8379)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-04-19 17:10:18 +05:30
Hariom BalharaandPeer Richelsen 39b93cdb5b Fix Editor not triggering setText (#8368)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-04-19 12:25:05 +02:00
Nafees Nazik e27fc05d5f fix: frontend issues and dark mode regressions (#8373) 2023-04-19 12:14:59 +02:00
Carina WollendorferandCarinaWolli 20c010fef1 Revert "Date override fixes (#8330)" (#8369)
release.yml / release (push) Canceled after 0s
This reverts commit 3ef328421f.

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-04-19 08:43:08 +00:00
Peer Richelsen 1a7911e9e2 Update package.json
release.yml / release (push) Canceled after 0s
2023-04-19 08:57:31 +02:00
Crowdin Bot cb09b5bc5f New Crowdin translations by Github Action 2023-04-19 06:47:15 +00:00
Efraín Rochín a99031bdd2 fix: [CAL-1490] Google Cal/Outlook not actively syncing/self hosted (#8305) 2023-04-19 08:46:17 +02:00
ada8a98362 chore: add database indexes (#8361)
Co-authored-by: Mythie <me@lucasjamessmith.me>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-04-18 16:50:42 -07:00
Yagiz Nizipli e176e81bae perf: improve bookings performance (#8362) 2023-04-18 16:48:53 -07:00
zomars b64dd66c2c Fixes bundle comments
https://github.com/hashicorp/nextjs-bundle-analysis/issues/52#issuecomment-1513913373
2023-04-18 16:40:06 -07:00
Crowdin Bot 7fd162601b New Crowdin translations by Github Action 2023-04-18 23:29:50 +00:00
c1f346c935 Cal 1099 availability section defined hour mismatch (#7416)
* CAL-1099 Add prop for availability in formMethods

* fix: show correct availabilty name

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

* CAL - 1099 / fix 12 hr time format bug

* fix: remove useEffect and fix vertical tab item ui

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

* Fixes merge conflicts

* Update index.tsx

* More conflict fixes

* Update packages/ui/components/navigation/tabs/VerticalTabs.tsx

* Type fixes

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: zomars <zomars@me.com>
2023-04-18 23:29:02 +00:00
Keith Williams 36d47bd2d8 v2.8.3
release.yml / release (push) Canceled after 0s
2023-04-18 18:22:24 -03:00
26cdb6521f perf: Adds PageWrapper to pages (#8344)
* Adds PageWrapper to pages

* Add missing PageWrapper

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-04-18 15:45:32 -03:00
Leo Giovanetti 014f1986de Adding missing mutation (#8357)
* Adding missing mutation

* Returning to prop for disabled
2023-04-18 16:48:19 +00:00
Carina WollendorferandCarinaWolli 3ef328421f Date override fixes (#8330)
* fix date override for fixed round robin + time zone in date override

* check if slot is within working hours of fixed hosts

* add test for date override in different time zone

* fix date overrides for not fixed hosts (round robin)

* code clean up

* fix added test

* use the correct timezone of user for date overrides

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-04-18 17:21:27 +02:00
Ben HybertandHariom Balhara c4fe69d826 Add Plausible (Custom) App (#8189)
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-04-18 19:24:51 +05:30
Hariom Balhara 6fd64b2209 Fix: cssVars API not working correctly on Booker Timeslot selection page (#8296)
* Use a method out of react scope to set CSS vars as that doesnt need react

* Refactor
2023-04-18 12:53:46 +00:00
Crowdin Bot 585e36793a New Crowdin translations by Github Action 2023-04-18 12:53:33 +00:00
Crowdin Bot 09b9370e13 New Crowdin translations by Github Action 2023-04-18 12:52:43 +00:00
Udit Takkar a76a4996b4 fix: address input (#8349)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-04-18 18:21:46 +05:30
Hariom Balhara b28d60b6dc API Support: Manage Booking Questions and responses (#8115) 2023-04-18 18:05:06 +05:30
Nafees Nazik a716eda14c fix: use the correct timezone of the user in booking detail page (#7915)
* fix: send timezone in query

* fiz: timezone

* fix: remove tz from createPayment

* fix: send timezone in query

* fiz: timezone

* fix: remove tz

* fix: remove tz

* fix: remove timezone query

* fix: timezone
2023-04-18 14:29:26 +02:00
Crowdin Bot 99f1524183 New Crowdin translations by Github Action 2023-04-18 12:10:14 +00:00
265 changed files with 4304 additions and 2101 deletions
+3
View File
@@ -79,6 +79,9 @@ CALENDSO_ENCRYPTION_KEY=
# Intercom Config
NEXT_PUBLIC_INTERCOM_APP_ID=
# Secret to enable Intercom Identity Verification
INTERCOM_SECRET=
# Zendesk Config
NEXT_PUBLIC_ZENDESK_KEY=
+1
View File
@@ -2,6 +2,7 @@ name: Cron - mark stale for inactive issues
permissions:
issues: write
pull-requests: write
on:
# "Scheduled workflows run on the latest commit on the default or base branch."
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
- name: Analyze bundle
run: |
cd apps/web
npx -p nextjs-bundle-analysis report
npx -p nextjs-bundle-analysis@0.5.0 report
- name: Upload bundle
uses: actions/upload-artifact@v2
@@ -76,7 +76,7 @@ jobs:
id: fc
with:
issue-number: ${{ github.event.number }}
body-includes: "<!-- __NEXTJS_BUNDLE -->"
body-includes: "<!-- __NEXTJS_BUNDLE_@calcom/web -->"
- name: Create Comment
uses: peter-evans/create-or-update-comment@v1.4.4
+9 -7
View File
@@ -1,7 +1,7 @@
import { isEmpty } from "lodash";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
import { getDirFromLang } from "@calcom/lib/i18n";
import { trpc } from "@calcom/trpc/react";
export function useViewerI18n() {
@@ -22,16 +22,18 @@ export function useViewerI18n() {
*/
const I18nLanguageHandler = (): null => {
const { i18n } = useTranslation("common");
const locale = useViewerI18n().data?.locale || "en";
const locale = useViewerI18n().data?.locale || i18n.language;
useEffect(() => {
if (locale && i18n.language && i18n.language !== locale) {
if (typeof i18n.changeLanguage === "function") i18n.changeLanguage(locale);
// bail early when i18n = {}
if (isEmpty(i18n)) return;
// if locale is ready and the i18n.language does != locale - changeLanguage
if (locale && i18n.language !== locale) {
i18n.changeLanguage(locale);
}
const dir = getDirFromLang(locale);
// set dir="rtl|ltr"
document.dir = i18n.dir();
document.documentElement.setAttribute("lang", locale);
document.documentElement.setAttribute("dir", dir);
}, [locale, i18n]);
return null;
-167
View File
@@ -1,167 +0,0 @@
import type { FormEvent } from "react";
import { useCallback, useEffect, useState } from "react";
import Cropper from "react-easy-crop";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui";
import type { Area } from "@lib/cropImage";
import { getCroppedImg } from "@lib/cropImage";
import { useFileReader } from "@lib/hooks/useFileReader";
import Slider from "@components/Slider";
type ImageUploaderProps = {
id: string;
buttonMsg: string;
handleAvatarChange: (imageSrc: string) => void;
imageSrc?: string;
target: string;
};
interface FileEvent<T = Element> extends FormEvent<T> {
target: EventTarget & T;
}
// This is separate to prevent loading the component until file upload
function CropContainer({
onCropComplete,
imageSrc,
}: {
imageSrc: string;
onCropComplete: (croppedAreaPixels: Area) => void;
}) {
const { t } = useLocale();
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1);
const handleZoomSliderChange = (value: number) => {
value < 1 ? setZoom(1) : setZoom(value);
};
return (
<div className="crop-container h-40 max-h-40 w-40 rounded-full">
<div className="relative h-40 w-40 rounded-full">
<Cropper
image={imageSrc}
crop={crop}
zoom={zoom}
aspect={1}
onCropChange={setCrop}
onCropComplete={(croppedArea, croppedAreaPixels) => onCropComplete(croppedAreaPixels)}
onZoomChange={setZoom}
/>
</div>
<Slider
value={zoom}
min={1}
max={3}
step={0.1}
label={t("slide_zoom_drag_instructions")}
changeHandler={handleZoomSliderChange}
/>
</div>
);
}
/** @deprecated Use `packages/ui/v2/core/ImageUploader.tsx` */
export default function ImageUploader({
target,
id,
buttonMsg,
handleAvatarChange,
...props
}: ImageUploaderProps) {
const { t } = useLocale();
const [imageSrc, setImageSrc] = useState<string | null>(null);
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
const [{ result }, setFile] = useFileReader({
method: "readAsDataURL",
});
useEffect(() => {
if (props.imageSrc) setImageSrc(props.imageSrc);
}, [props.imageSrc]);
const onInputFile = (e: FileEvent<HTMLInputElement>) => {
if (!e.target.files?.length) {
return;
}
setFile(e.target.files[0]);
};
const showCroppedImage = useCallback(
async (croppedAreaPixels: Area | null) => {
try {
if (!croppedAreaPixels) return;
const croppedImage = await getCroppedImg(
result as string /* result is always string when using readAsDataUrl */,
croppedAreaPixels
);
setImageSrc(croppedImage);
handleAvatarChange(croppedImage);
} catch (e) {
console.error(e);
}
},
[result, handleAvatarChange]
);
return (
<Dialog
onOpenChange={
(opened) => !opened && setFile(null) // unset file on close
}>
<DialogTrigger asChild>
<div className="flex items-center">
<Button color="secondary" type="button" className="py-1 text-xs">
{buttonMsg}
</Button>
</div>
</DialogTrigger>
<DialogContent>
<div className="mb-4 sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
<h3 className="font-cal text-emphasis text-lg leading-6" id="modal-title">
{t("upload_target", { target })}
</h3>
</div>
</div>
<div className="mb-4">
<div className="cropper mt-6 flex flex-col items-center justify-center p-8">
{!result && (
<div className="bg-muted flex h-20 max-h-20 w-20 items-center justify-start rounded-full">
{!imageSrc && (
<p className="text-inverted w-full text-center text-sm sm:text-xs">
{t("no_target", { target })}
</p>
)}
{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} />}
<label className="border-default bg-default hover:bg-mutedover:text-emphasis dark:hover:bg-inverted text-default dark:text-inverted focus:ring-empthasis mt-8 rounded-sm border px-3 py-1 text-xs font-medium leading-4 focus:outline-none focus:ring-2 focus:ring-offset-1 dark:border-gray-800 dark:bg-transparent">
<input
onInput={onInputFile}
type="file"
name={id}
placeholder={t("upload_image")}
className="pointer-events-none absolute mt-4 opacity-0"
accept="image/*"
/>
{t("choose_a_file")}
</label>
</div>
</div>
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</DialogClose>
<DialogClose color="secondary">{t("cancel")}</DialogClose>
</div>
</DialogContent>
</Dialog>
);
}
+2 -2
View File
@@ -1,4 +1,3 @@
import { AdminRequired } from "components/ui/AdminRequired";
import { noop } from "lodash";
import type { LinkProps } from "next/link";
import Link from "next/link";
@@ -6,6 +5,7 @@ import { useRouter } from "next/router";
import type { FC, MouseEventHandler } from "react";
import { Fragment } from "react";
import { PermissionContainer } from "@calcom/features/auth/PermissionContainer";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@lib/classNames";
@@ -60,7 +60,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
}
: noop;
const Component = tab.adminRequired ? AdminRequired : Fragment;
const Component = tab.adminRequired ? PermissionContainer : Fragment;
const className = tab.className || "";
return (
<Component key={tab.name}>
+82
View File
@@ -0,0 +1,82 @@
import { DefaultSeo } from "next-seo";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
import Script from "next/script";
import "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import type { AppProps } from "@lib/app-providers";
import AppProviders from "@lib/app-providers";
import { seoConfig } from "@lib/config/next-seo.config";
import I18nLanguageHandler from "@components/I18nLanguageHandler";
export interface CalPageWrapper {
(props?: any): JSX.Element;
PageWrapper?: AppProps["Component"]["PageWrapper"];
}
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
const calFont = localFont({
src: "../fonts/CalSans-SemiBold.woff2",
variable: "--font-cal",
preload: true,
display: "swap",
});
function PageWrapper(props: AppProps) {
const { Component, pageProps, err, router } = props;
let pageStatus = "200";
if (router.pathname === "/404") {
pageStatus = "404";
} else if (router.pathname === "/500") {
pageStatus = "500";
}
// On client side don't let nonce creep into DOM
// It also avoids hydration warning that says that Client has the nonce value but server has "" because browser removes nonce attributes before DOM is built
// See https://github.com/kentcdodds/nonce-hydration-issues
// Set "" only if server had it set otherwise keep it undefined because server has to match with client to avoid hydration error
const nonce = typeof window !== "undefined" ? (pageProps.nonce ? "" : undefined) : pageProps.nonce;
const providerProps = {
...props,
pageProps: {
...props.pageProps,
nonce,
},
};
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page);
return (
<AppProviders {...providerProps}>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<I18nLanguageHandler />
<Script
nonce={nonce}
id="page-status"
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
/>
<style jsx global>{`
:root {
--font-inter: ${interFont.style.fontFamily};
--font-cal: ${calFont.style.fontFamily};
}
`}</style>
{getLayout(
Component.requiresLicense ? (
<LicenseRequired>
<Component {...pageProps} err={err} />
</LicenseRequired>
) : (
<Component {...pageProps} err={err} />
),
router
)}
</AppProviders>
);
}
export default PageWrapper;
+1 -1
View File
@@ -46,7 +46,7 @@ export default function SettingsShell({
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
return (
<Shell {...rest}>
<Shell {...rest} hideHeadingOnMobile>
<div className="sm:mx-auto">
<NavTabs tabs={tabs} />
</div>
-27
View File
@@ -1,27 +0,0 @@
import * as SliderPrimitive from "@radix-ui/react-slider";
import React from "react";
const Slider = ({
value,
label,
changeHandler,
...props
}: Omit<SliderPrimitive.SliderProps, "value"> & {
value: number;
label: string;
changeHandler: (value: number) => void;
}) => (
<SliderPrimitive.Root
className="slider mt-2"
value={[value]}
aria-label={label}
onValueChange={(value: number[]) => changeHandler(value[0] ?? value)}
{...props}>
<SliderPrimitive.Track className="slider-track">
<SliderPrimitive.Range className="slider-range" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="slider-thumb" />
</SliderPrimitive.Root>
);
export default Slider;
+4 -4
View File
@@ -6,7 +6,7 @@ import React, { useState } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { InstallAppButton, AppDependencyComponent } from "@calcom/app-store/components";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import Shell from "@calcom/features/shell/Shell";
import classNames from "@calcom/lib/classNames";
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
@@ -41,7 +41,7 @@ const Component = ({
isTemplate,
dependencies,
}: Parameters<typeof App>[0]) => {
const { t } = useLocale();
const { t, i18n } = useLocale();
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
const router = useRouter();
@@ -247,7 +247,7 @@ const Component = ({
t("free_to_use_apps")
) : (
<>
{Intl.NumberFormat("en-US", {
{Intl.NumberFormat(i18n.language, {
style: "currency",
currency: "USD",
useGrouping: false,
@@ -366,7 +366,7 @@ export default function App(props: {
dependencies?: string[];
}) {
return (
<Shell smallHeading isPublic heading={<ShellHeading />} backPath="/apps" withoutSeo>
<Shell smallHeading isPublic hideHeadingOnMobile heading={<ShellHeading />} backPath="/apps" withoutSeo>
<HeadSeo
title={props.name}
description={props.description}
@@ -22,7 +22,7 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
if (session.status === "loading") return <></>;
return (
<Shell {...rest} actions={actions?.("block")}>
<Shell {...rest} actions={actions?.("block")} hideHeadingOnMobile>
<div className="flex flex-col xl:flex-row">
<main className="w-full">
{emptyStore ? (
@@ -57,7 +57,7 @@ export default function InstalledAppsLayout({
}
return (
<Shell {...rest}>
<Shell {...rest} hideHeadingOnMobile>
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
{children}
</AppCategoryNavigation>
@@ -41,7 +41,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
return (
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
{eventLocationType.iconUrl === "/link.svg" ? (
<Link className="text-default min-h-4 min-w-4 ml-[2px] opacity-70 ltr:mr-[10px] rtl:ml-[10px] " />
<Link className="text-default ml-[2px] h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
) : (
<img
src={eventLocationType.iconUrl}
@@ -1,5 +1,5 @@
import { i18n } from "next-i18next";
import type { TFunction } from "next-i18next";
import { FormattedNumber, IntlProvider } from "react-intl";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { CreditCard } from "@calcom/ui/components/icon";
@@ -7,28 +7,29 @@ import { CreditCard } from "@calcom/ui/components/icon";
const BookingDescriptionPayment = (props: {
eventType: Parameters<typeof getPaymentAppData>[0];
t: TFunction;
i18n: typeof i18n;
}) => {
const paymentAppData = getPaymentAppData(props.eventType);
if (!paymentAppData || paymentAppData.price <= 0) return null;
const params = {
amount: paymentAppData.price / 100.0,
formatParams: { amount: { currency: paymentAppData.currency } },
};
return (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
{paymentAppData.paymentOption === "HOLD" ? (
<>
{props.t("no_show_fee_amount", {
amount: paymentAppData.price / 100.0,
formatParams: { amount: { currency: paymentAppData.currency } },
})}
</>
<>{props.t("no_show_fee_amount", params)}</>
) : (
<IntlProvider locale="en">
<FormattedNumber
value={paymentAppData.price / 100.0}
style="currency"
currency={paymentAppData.currency?.toUpperCase()}
/>
</IntlProvider>
<>
{/* If undefined this will default to the browser locale */}
{new Intl.NumberFormat(i18n?.language, {
style: "currency",
currency: paymentAppData.currency,
}).format(paymentAppData.price / 100)}
</>
)}
</p>
);
@@ -10,6 +10,7 @@ import "@calcom/dayjs/locales";
import ViewRecordingsDialog from "@calcom/features/ee/video/ViewRecordingsDialog";
import classNames from "@calcom/lib/classNames";
import { formatTime } from "@calcom/lib/date-fns";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
@@ -86,6 +87,8 @@ function BookingListItem(booking: BookingItemProps) {
const isTabRecurring = booking.listingStatus === "recurring";
const isTabUnconfirmed = booking.listingStatus === "unconfirmed";
const paymentAppData = getPaymentAppData(booking.eventType);
const bookingConfirm = async (confirm: boolean) => {
let body = {
bookingId: booking.id,
@@ -413,7 +416,7 @@ function BookingListItem(booking: BookingItemProps) {
{title}
<span> </span>
{!!booking?.eventType?.price && !booking.paid && (
{paymentAppData.enabled && !booking.paid && booking.payment.length && (
<Badge className="ms-2 me-2 hidden sm:inline-flex" variant="orange">
{t("pending_payment")}
</Badge>
+1 -1
View File
@@ -29,7 +29,7 @@ const TimeOptions: FC<Props> = ({ onSelectTimeZone }) => {
classNames={{
singleValue: () => "text-default",
dropdownIndicator: () => "text-default",
menu: () => "!w-64 max-w-[90vw]",
menu: () => "!w-64 max-w-[90vw] shadow-dropdown bg-default border-subtle border rounded-md mt-1",
}}
variant="minimal"
value={selectedTimeZone}
@@ -1,7 +1,6 @@
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import { useEffect, useMemo, useReducer, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
import { z } from "zod";
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
@@ -37,7 +36,7 @@ import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
const PoweredByCal = dynamic(() => import("@components/ui/PoweredByCal"));
const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy"));
const Toaster = dynamic(() => import("react-hot-toast").then((mod) => mod.Toaster), { ssr: false });
/*const SlotPicker = dynamic(() => import("../SlotPicker").then((mod) => mod.SlotPicker), {
@@ -75,7 +74,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
brandColor: profile.brandColor,
darkBrandColor: profile.darkBrandColor,
});
const { t } = useLocale();
const { t, i18n } = useLocale();
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
@@ -124,16 +123,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
[timeZone]
);
const paymentAppData = getPaymentAppData(eventType);
const paymentAmount = () => {
return;
<IntlProvider locale="en">
<FormattedNumber
value={paymentAppData.price / 100.0}
style="currency"
currency={paymentAppData.currency?.toUpperCase()}
/>
</IntlProvider>;
};
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
@@ -257,13 +247,12 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
})}
</>
) : (
<IntlProvider locale="en">
<FormattedNumber
value={paymentAppData.price / 100.0}
style="currency"
currency={paymentAppData.currency?.toUpperCase()}
/>
</IntlProvider>
<>
{new Intl.NumberFormat(i18n.language, {
style: "currency",
currency: paymentAppData.currency,
}).format(paymentAppData.price / 100)}
</>
)}
</p>
)}
@@ -312,7 +301,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
</div>
</div>
{/* FIXME: We don't show branding in Embed yet because we need to place branding on top of the main content. Keeping it outside the main content would have visibility issues because outside main content background is transparent */}
{!restProps.isBrandingHidden && !isEmbed && <PoweredByCal />}
{!restProps.isBrandingHidden && !isEmbed && <PoweredBy />}
</div>
</main>
</div>
@@ -554,7 +554,7 @@ const BookingPage = ({
{showEventTypeDetails && (
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
<BookingDescriptionPayment eventType={eventType} t={t} />
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="dark:text-inverted text-default items-start text-sm font-medium">
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
@@ -1,7 +1,5 @@
import { Trans } from "next-i18next";
import { useState } from "react";
import type { Dispatch, SetStateAction } from "react";
import { IntlProvider, FormattedNumber } from "react-intl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -40,6 +38,11 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
},
});
const currencyStringParams = {
amount: props.paymentAmount / 100.0,
formatParams: { amount: { currency: props.paymentCurrency } },
};
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
<DialogContent>
@@ -49,19 +52,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
</div>
<div className="pt-1">
<DialogHeader title={t("charge_card")} />
<Trans i18nKey="charge_card_dialog_body">
<p className="text-sm text-gray-500">
You are about to charge the attendee{" "}
<IntlProvider locale="en">
<FormattedNumber
value={props.paymentAmount / 100.0}
style="currency"
currency={props.paymentCurrency?.toUpperCase()}
/>
</IntlProvider>
. Are you sure you want to continue?
</p>
</Trans>
<p>{t("charge_card_dialog_body", currencyStringParams)}</p>
{chargeError && (
<div className="mt-4 flex text-red-500">
@@ -80,16 +71,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
bookingId,
})
}>
<Trans i18nKey="charge_card_confirm">
Charge attendee{" "}
<IntlProvider locale="en">
<FormattedNumber
value={props.paymentAmount / 100.0}
style="currency"
currency={props.paymentCurrency?.toUpperCase()}
/>
</IntlProvider>
</Trans>
{t("charge_attendee", currencyStringParams)}
</Button>
</DialogFooter>
</div>
@@ -174,7 +174,6 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
id="locationInput"
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
required
className="border-default block w-full rounded-sm text-sm"
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
defaultValue={
defaultLocation ? defaultLocation[eventLocationType.defaultValueVariable] : undefined
@@ -106,7 +106,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
{!shouldLockDisableProps("apps").disabled && (
<div>
{!isLoading && notInstalledApps?.length ? (
<h2 className="text-emphasis mt-0 mb-2 text-lg font-semibold">{t("available_apps")}</h2>
<h2 className="text-emphasis my-2 text-lg font-semibold">{t("available_apps")}</h2>
) : null}
<div className="before:border-0">
{notInstalledApps?.map((app) => (
@@ -1,21 +1,23 @@
import { SchedulingType } from "@prisma/client";
import type { FormValues, EventTypeSetup } from "pages/event-types/[type]";
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import type { OptionProps, SingleValueProps } from "react-select";
import { components } from "react-select";
import dayjs from "@calcom/dayjs";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { NewScheduleButton } from "@calcom/features/schedules";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { weekdayNames } from "@calcom/lib/weekday";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Badge, Button, Select, SettingsToggle, SkeletonText, EmptyScreen } from "@calcom/ui";
import { ExternalLink, Globe, Clock } from "@calcom/ui/components/icon";
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { ExternalLink, Globe } from "@calcom/ui/components/icon";
type AvailabilityOption = {
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
export type AvailabilityOption = {
label: string;
value: number;
isDefault: boolean;
@@ -62,40 +64,12 @@ const SingleValue = ({ ...props }: SingleValueProps<AvailabilityOption>) => {
);
};
const AvailabilitySelect = ({
className = "",
options,
value,
...props
}: {
className?: string;
name: string;
value: AvailabilityOption | undefined;
options: AvailabilityOption[];
isDisabled?: boolean;
onBlur: () => void;
onChange: (value: AvailabilityOption | null) => void;
}) => {
const { t } = useLocale();
return (
<Select
placeholder={t("select")}
options={options}
isDisabled={props.isDisabled}
isSearchable={false}
onChange={props.onChange}
className={classNames("block w-full min-w-0 flex-1 rounded-sm text-sm", className)}
defaultValue={value}
components={{ Option, SingleValue }}
isMulti={false}
/>
);
};
const format = (date: Date, hour12: boolean) =>
Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "numeric", hour12 }).format(
new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss"))
);
Intl.DateTimeFormat(undefined, {
hour: "numeric",
minute: "numeric",
hourCycle: hour12 ? "h12" : "h24",
}).format(new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss")));
const EventTypeScheduleDetails = ({
isManagedEventType,
@@ -188,61 +162,57 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
);
const { watch } = useFormContext<FormValues>();
const watchSchedule = watch("schedule");
const formMethods = useFormContext<FormValues>();
const [options, setOptions] = useState<AvailabilityOption[]>([]);
const { data, isLoading } = trpc.viewer.availability.list.useQuery();
const { isLoading } = trpc.viewer.availability.list.useQuery(undefined, {
onSuccess: ({ schedules }) => {
const options = schedules.map((schedule) => ({
value: schedule.id,
label: schedule.name,
isDefault: schedule.isDefault,
isManaged: false,
}));
if (!data?.schedules.length && !isLoading)
return (
<EmptyScreen
Icon={Clock}
headline={t("new_schedule_heading")}
description={t("new_schedule_description")}
buttonRaw={<NewScheduleButton fromEventType />}
border={false}
/>
);
// We are showing a managed event for a team admin, so adding the option to let members choose their schedule
if (isManagedEventType) {
options.push({
value: 0,
label: t("members_default_schedule"),
isDefault: false,
isManaged: false,
});
}
const schedules = data?.schedules || [];
// We are showing a managed event for a member and team owner selected their own schedule, so adding
// the managed schedule option
if (
isChildrenManagedEventType &&
watchSchedule &&
!schedules.find((schedule) => schedule.id === watchSchedule)
) {
options.push({
value: watchSchedule,
label: eventType.scheduleName ?? t("default_schedule_name"),
isDefault: false,
isManaged: false,
});
}
const options = schedules.map((schedule) => ({
value: schedule.id,
label: schedule.name,
isDefault: schedule.isDefault,
isManaged: false,
}));
setOptions(options);
// We are showing a managed event for a team admin, so adding the option to let members choose their schedule
if (isManagedEventType) {
options.push({
value: 0,
label: t("members_default_schedule"),
isDefault: false,
isManaged: false,
});
}
const scheduleId = formMethods.getValues("schedule");
const value = options.find((option) =>
scheduleId
? option.value === scheduleId
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
);
// We are showing a managed event for a member and team owner selected their own schedule, so adding
// the managed schedule option
if (
isChildrenManagedEventType &&
watchSchedule &&
!schedules.find((schedule) => schedule.id === watchSchedule)
) {
options.push({
value: watchSchedule,
label: eventType.scheduleName ?? t("default_schedule_name"),
isDefault: false,
isManaged: false,
});
}
formMethods.setValue("availability", value);
},
});
const value = options.find((option) =>
watchSchedule
? option.value === watchSchedule
: isManagedEventType
? option.value === 0
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
);
const availabilityValue = formMethods.watch("availability");
return (
<div className="space-y-4">
@@ -251,27 +221,33 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
{t("availability")}
{shouldLockIndicator("availability")}
</label>
<Controller
name="schedule"
render={({ field }) => (
<>
<AvailabilitySelect
value={value}
options={options}
onBlur={field.onBlur}
isDisabled={shouldLockDisableProps("schedule").disabled}
name={field.name}
onChange={(selected) => {
field.onChange(selected?.value || null);
}}
/>
</>
)}
/>
{isLoading && <SelectSkeletonLoader />}
{!isLoading && (
<Controller
name="schedule"
render={({ field }) => {
return (
<Select
placeholder={t("select")}
options={options}
isSearchable={false}
onChange={(selected) => {
field.onChange(selected?.value || null);
if (selected?.value) formMethods.setValue("availability", selected);
}}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
value={availabilityValue}
components={{ Option, SingleValue }}
isMulti={false}
/>
);
}}
/>
)}
</div>
{value?.value !== 0 ? (
{availabilityValue?.value !== 0 ? (
<EventTypeScheduleDetails
selectedScheduleValue={value}
selectedScheduleValue={availabilityValue}
isManagedEventType={isManagedEventType || isChildrenManagedEventType}
/>
) : (
@@ -12,6 +12,7 @@ import { z } from "zod";
import type { EventLocationType } from "@calcom/app-store/locations";
import { getEventLocationType, MeetLocationType, LocationType } from "@calcom/app-store/locations";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import cx from "@calcom/lib/classNames";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
@@ -80,6 +81,7 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
const [mounted, setIsMounted] = useState(false);
const { t } = useLocale();
const { description } = props;
const [firstRender, setFirstRender] = useState(true);
useEffect(() => {
setIsMounted(true);
}, []);
@@ -91,6 +93,8 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
excludedToolbarItems={["blockType"]}
placeholder={t("quick_video_meeting")}
editable={props.editable}
firstRender={firstRender}
setFirstRender={setFirstRender}
/>
) : (
<SkeletonContainer>
@@ -277,7 +281,13 @@ export const EventSetupTab = (
<div className="flex items-center">
<img
src={eventLocationType.iconUrl}
className="h-4 w-4"
className={cx(
"h-4 w-4",
// invert all the icons except app icons
eventLocationType.iconUrl &&
!eventLocationType.iconUrl.startsWith("/api") &&
"dark:invert"
)}
alt={`${eventLocationType.label} logo`}
/>
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
@@ -51,6 +51,7 @@ import {
} from "@calcom/ui/components/icon";
import { EmbedButton, EmbedDialog } from "@components/Embed";
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
type Props = {
children: React.ReactNode;
@@ -63,6 +64,7 @@ type Props = {
enabledWorkflowsNumber: number;
formMethods: UseFormReturn<FormValues>;
isUpdateMutationLoading?: boolean;
availability?: AvailabilityOption;
};
function getNavigation(props: {
@@ -71,8 +73,10 @@ function getNavigation(props: {
enabledAppsNumber: number;
enabledWorkflowsNumber: number;
installedAppsNumber: number;
availability: AvailabilityOption | undefined;
}) {
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber, availability } =
props;
const duration =
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
@@ -128,6 +132,7 @@ function EventTypeSingleLayout({
enabledWorkflowsNumber,
isUpdateMutationLoading,
formMethods,
availability,
}: Props) {
const utils = trpc.useContext();
const { t } = useLocale();
@@ -171,6 +176,7 @@ function EventTypeSingleLayout({
enabledAppsNumber,
installedAppsNumber,
enabledWorkflowsNumber,
availability,
});
navigation.splice(1, 0, {
name: "availability",
@@ -214,7 +220,7 @@ function EventTypeSingleLayout({
});
}
return navigation;
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team]);
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team, availability]);
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
eventType.slug
@@ -374,6 +380,8 @@ function EventTypeSingleLayout({
tabs={EventTypeTabs}
sticky
linkProps={{ shallow: true }}
itemClassname="items-start"
iconClassName="md:mt-px"
/>
</div>
<div className="p-2 md:mx-0 md:p-0 xl:hidden">
@@ -50,7 +50,7 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
const updateSchedule = trpc.viewer.availability.schedule.update.useMutation(mutationOptions);
return (
<Form
className="bg-default dark:text-inverted text-emphasis w-full dark:bg-opacity-5"
className="bg-default dark:text-inverted text-emphasis w-full [--cal-brand-accent:#fafafa] dark:bg-opacity-5"
form={availabilityForm}
handleSubmit={async (values) => {
try {
@@ -35,6 +35,7 @@ const UserProfile = (props: IUserProfileProps) => {
const router = useRouter();
const createEventType = trpc.viewer.eventTypes.create.useMutation();
const telemetry = useTelemetry();
const [firstRender, setFirstRender] = useState(true);
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async (_data, context) => {
@@ -147,6 +148,8 @@ const UserProfile = (props: IUserProfileProps) => {
getText={() => md.render(getValues("bio") || user?.bio || "")}
setText={(value: string) => setValue("bio", turndown(value))}
excludedToolbarItems={["blockType"]}
firstRender={firstRender}
setFirstRender={setFirstRender}
/>
<p className="dark:text-inverted text-default mt-2 font-sans text-sm font-normal">
{t("few_sentences_about_yourself")}
-17
View File
@@ -1,17 +0,0 @@
import { useSession } from "next-auth/react";
import type { FC } from "react";
import { Fragment } from "react";
type AdminRequiredProps = {
as?: keyof JSX.IntrinsicElements;
children?: React.ReactNode;
};
/** @deprecated use PermssionContainer instead. Will delete once V2 goes live */
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>;
};
+2 -6
View File
@@ -1,7 +1,6 @@
import classNames from "classnames";
import { APP_NAME, LOGO } from "@calcom/lib/constants";
import { HeadSeo } from "@calcom/ui";
import { HeadSeo, Logo } from "@calcom/ui";
import Loader from "@components/Loader";
@@ -18,10 +17,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
return (
<div className="flex min-h-screen flex-col justify-center bg-[#f3f4f6] py-12 sm:px-6 lg:px-8">
<HeadSeo title={props.title} description={props.description} />
{props.showLogo && (
// eslint-disable-next-line @next/next/no-img-element
<img className="mb-auto h-4" src={LOGO} alt={`${APP_NAME} Logo`} />
)}
{props.showLogo && <Logo small inline={false} className="mx-auto mb-auto" />}
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
{props.heading && <h2 className="font-cal text-emphasis text-center text-3xl">{props.heading}</h2>}
+5 -3
View File
@@ -16,8 +16,10 @@ const EditableHeading = function EditableHeading({
const [isEditing, setIsEditing] = useState(false);
const enableEditing = () => setIsEditing(true);
return (
<div className="group relative cursor-pointer" onClick={enableEditing}>
<div className="flex items-center">
<div
className="group pointer-events-none relative truncate sm:pointer-events-auto"
onClick={enableEditing}>
<div className="flex cursor-pointer items-center">
<label className="min-w-8 relative inline-block">
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value}&nbsp;</span>
{!isEditing && isReady && (
@@ -29,7 +31,7 @@ const EditableHeading = function EditableHeading({
value={value}
required
className={classNames(
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
)}
onFocus={(e) => {
setIsEditing(true);
-29
View File
@@ -1,29 +0,0 @@
import Link from "next/link";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { POWERED_BY_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
const PoweredByCal = () => {
const { t } = useLocale();
const isEmbed = useIsEmbed();
return (
<div className={"p-2 text-center text-xs sm:text-right" + (isEmbed ? " max-w-3xl" : "")}>
<Link href={POWERED_BY_URL} target="_blank" className="text-subtle opacity-50 hover:opacity-100">
{t("powered_by")}{" "}
<img
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="/cal-logo-word.svg"
alt="Cal.com Logo"
/>
<img
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
src="/cal-logo-word-dark.svg"
alt="Cal.com Logo"
/>
</Link>
</div>
);
};
export default PoweredByCal;
@@ -195,7 +195,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<span
className={classNames(
isInputUsernamePremium ? "border border-orange-400 " : "",
"border-default bg-muted text-subtle hidden h-9 items-center rounded-l-md border border-r-0 border-r-gray-300 px-3 text-sm md:inline-flex"
"border-default bg-muted text-subtle hidden h-9 items-center rounded-l-md border border-r-0 px-3 text-sm md:inline-flex"
)}>
{process.env.NEXT_PUBLIC_WEBSITE_URL.replace("https://", "").replace("http://", "")}/
</span>
@@ -215,7 +215,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
: "border focus:border",
markAsError
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none"
: "border-l-gray-300",
: "border-l-default",
disabled ? "bg-subtle text-muted focus:border-0" : ""
)}
value={inputUsernameValue}
@@ -234,8 +234,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
isInputUsernamePremium ? "text-orange-400" : "",
usernameIsAvailable ? "" : ""
)}>
{isInputUsernamePremium ? <StarIconSolid className="mt-[2px] w-6" /> : <></>}
{!isInputUsernamePremium && usernameIsAvailable ? <Check className="mt-2 w-6" /> : <></>}
{isInputUsernamePremium ? <StarIconSolid className="mt-[2px] h-4 w-4" /> : <></>}
{!isInputUsernamePremium && usernameIsAvailable ? <Check className="mt-2 h-4 w-4" /> : <></>}
</span>
</div>
</div>
@@ -136,8 +136,8 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
/>
{currentUsername !== inputUsernameValue && (
<div className="absolute right-[2px] top-6 flex flex-row">
<span className={classNames("mx-2 py-2")}>
{usernameIsAvailable ? <Check className="w-6" /> : <></>}
<span className={classNames("mx-2 py-2.5")}>
{usernameIsAvailable ? <Check className="h-4 w-4" /> : <></>}
</span>
</div>
)}
@@ -49,7 +49,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
{...rest}
ref={ref}
type="checkbox"
className="text-primary-600 focus:ring-primary-500 border-default h-4 w-4 rounded"
className="text-primary-600 focus:ring-primary-500 border-default bg-default h-4 w-4 rounded"
/>
</div>
<span className="ms-3 text-sm">{description}</span>
@@ -3,6 +3,7 @@ import { components } from "react-select";
import type { EventLocationType } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import cx from "@calcom/lib/classNames";
import { Select } from "@calcom/ui";
export type LocationOption = {
@@ -19,10 +20,14 @@ export type GroupOptionType = GroupBase<LocationOption>;
const OptionWithIcon = ({ icon, label }: { icon?: string; label: string }) => {
return (
<div className="flex items-center gap-3">
{/* TODO: figure out a way to invert icons when in dark mode. We can't just
dark:invert due to google meet cal etc all breaking when we do this
*/}
{icon && <img src={icon} alt="cover" className="h-3.5 w-3.5 dark:hidden" />}
{icon && (
<img
src={icon}
alt="cover"
// invert all the icons except app icons
className={cx("h-3.5 w-3.5", icon && !icon.startsWith("/api") && "dark:invert")}
/>
)}
<span className={classNames("text-sm font-medium")}>{label}</span>
</div>
);
+2
View File
@@ -30,6 +30,7 @@ export type AppProps = Omit<NextAppProps<WithNonceProps & Record<string, unknown
isThemeSupported?: boolean;
isBookingPage?: boolean | ((arg: { router: NextRouter }) => boolean);
getLayout?: (page: React.ReactElement, router: NextRouter) => ReactNode;
PageWrapper?: (props: AppProps) => JSX.Element;
};
/** Will be defined only is there was an error */
@@ -154,6 +155,7 @@ const AppProviders = (props: AppPropsWithChildren) => {
<SessionProvider session={session || undefined}>
<CustomI18nextProvider {...props}>
<TooltipProvider>
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
<CalcomThemeProvider
nonce={props.pageProps.nonce}
isThemeSupported={props.Component.isThemeSupported}
+13 -14
View File
@@ -7,9 +7,16 @@ import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { isIpInBanlist } from "@calcom/lib/getIP";
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
let cold = true;
const middleware: NextMiddleware = async (req) => {
const url = req.nextUrl;
const requestHeaders = new Headers(req.headers);
// This console.log is required to create a report in axios for hot and cold requests
console.log(cold ? "Cold Start" : "Hot Start");
requestHeaders.set("x-cal-cold-start", cold ? "true" : "false");
cold = false;
if (!url.pathname.startsWith("/api")) {
//
// NOTE: When tRPC hits an error a 500 is returned, when this is received
@@ -61,27 +68,19 @@ const middleware: NextMiddleware = async (req) => {
}
if (url.pathname.startsWith("/api/trpc/")) {
const requestHeaders = new Headers(req.headers);
requestHeaders.set("x-cal-timezone", req.headers.get("x-vercel-ip-timezone") ?? "");
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
if (url.pathname.startsWith("/auth/login")) {
const moreHeaders = new Headers(req.headers);
// Use this header to actually enforce CSP, otherwise it is running in Report Only mode on all pages.
moreHeaders.set("x-csp-enforce", "true");
return NextResponse.next({
request: {
headers: moreHeaders,
},
});
requestHeaders.set("x-csp-enforce", "true");
}
return NextResponse.next();
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
};
export const config = {
+11
View File
@@ -98,6 +98,12 @@ const nextConfig = {
transform: "lucide-react/dist/esm/icons/{{ kebabCase member }}",
preventFullImport: true,
},
"@heroicons/react/solid": {
transform: "@heroicons/react/solid/esm/{{ member }}",
},
"@heroicons/react/outline": {
transform: "@heroicons/react/outline/esm/{{ member }}",
},
"@calcom/features/insights/components": {
transform: "@calcom/features/insights/components/{{member}}",
skipDefaultConversion: true,
@@ -291,6 +297,11 @@ const nextConfig = {
destination: "/api/link?action=:action&email=:email&bookingUid=:bookingUid&oldToken=:oldToken",
permanent: true,
},
{
source: "/support",
destination: "/event-types?openIntercom=true",
permanent: true,
},
];
if (process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com") {
+1 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.8.2",
"version": "2.8.9",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -115,8 +115,6 @@
"react-select": "^5.7.0",
"react-timezone-select": "^1.4.0",
"react-use-intercom": "1.5.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.7",
"remark": "^14.0.2",
"rrule": "^2.7.1",
"sanitize-html": "^2.10.0",
@@ -155,8 +153,6 @@
"@types/qrcode": "^1.4.3",
"@types/react": "18.0.26",
"@types/react-phone-number-input": "^3.0.14",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/sanitize-html": "^2.9.0",
"@types/stripe": "^8.0.417",
"@types/uuid": "8.3.1",
+4
View File
@@ -8,6 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HeadSeo } from "@calcom/ui";
import { BookOpen, Check, ChevronRight, FileText } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import { ssgInit } from "@server/lib/ssg";
export default function Custom404() {
@@ -366,6 +368,8 @@ export default function Custom404() {
);
}
Custom404.PageWrapper = PageWrapper;
export const getStaticProps = async (context: GetStaticPropsContext) => {
const ssr = await ssgInit(context);
+4
View File
@@ -6,6 +6,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, showToast } from "@calcom/ui";
import { Copy } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
export default function Error500() {
const { t } = useLocale();
const router = useRouter();
@@ -52,3 +54,5 @@ export default function Error500() {
</div>
);
}
Error500.PageWrapper = PageWrapper;
+6 -3
View File
@@ -32,6 +32,8 @@ import { ArrowRight } from "@calcom/ui/components/icon";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
@@ -57,7 +59,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
{eventTypes.map((type, index) => (
<li
key={index}
className=" border-subtle bg-default hover:bg-muted group relative border-b first:rounded-t-md last:rounded-b-md last:border-b-0">
className=" border-subtle bg-default dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b first:rounded-t-md last:rounded-b-md last:border-b-0">
<ArrowRight className="text-emphasis absolute right-3 top-3 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100" />
<Link
href={getUsernameSlugLink({ users: props.users, slug: type.slug })}
@@ -121,7 +123,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
<main
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? " border-booker border-booker-width bg-default rounded-md border" : "",
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
"max-w-3xl py-24 px-4"
)}>
{isSingleUser && ( // When we deal with a single user, not dynamic group
@@ -159,7 +161,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
<div
key={type.id}
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
className=" border-subtle bg-default hover:bg-muted group relative border-b first:rounded-t-md last:rounded-b-md last:border-b-0">
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b first:rounded-t-md last:rounded-b-md last:border-b-0">
<ArrowRight className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100" />
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
<div className="block w-full p-5">
@@ -196,6 +198,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
}
User.isBookingPage = true;
User.PageWrapper = PageWrapper;
const getEventTypesWithHiddenFromDB = async (userId: number) => {
return (
+2
View File
@@ -12,6 +12,7 @@ import { isBrandingHidden } from "@lib/isBrandingHidden";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import PageWrapper from "@components/PageWrapper";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps> & EmbedProps;
@@ -55,6 +56,7 @@ export default function Type(props: AvailabilityPageProps) {
}
Type.isBookingPage = true;
Type.PageWrapper = PageWrapper;
const paramsSchema = z.object({ type: z.string(), user: z.string() });
async function getUserPageProps(context: GetStaticPropsContext) {
+2
View File
@@ -26,6 +26,7 @@ import {
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import BookingPage from "@components/booking/pages/BookingPage";
import { ssrInit } from "@server/lib/ssr";
@@ -70,6 +71,7 @@ export default function Book(props: BookPageProps) {
}
Book.isBookingPage = true;
Book.PageWrapper = PageWrapper;
const querySchema = z.object({
bookingUid: z.string().optional(),
@@ -18,7 +18,7 @@ export const getStaticProps: GetStaticProps<
{ user: string }
> = async (context) => {
const { user: username, month } = paramsSchema.parse(context.params);
const user = await prisma.user.findUnique({
const userWithCredentials = await prisma.user.findUnique({
where: {
username,
},
@@ -34,14 +34,15 @@ export const getStaticProps: GetStaticProps<
).startOf("day");
const endDate = startDate.endOf("month");
try {
const results = user?.credentials
const results = userWithCredentials?.credentials
? await getCachedResults(
user?.credentials,
userWithCredentials?.credentials,
startDate.format(),
endDate.format(),
user?.selectedCalendars
userWithCredentials?.selectedCalendars
)
: [];
return {
props: { results, date: new Date().toISOString() },
revalidate: 1,
+3 -91
View File
@@ -1,101 +1,13 @@
import { DefaultSeo } from "next-seo";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
import Head from "next/head";
import Script from "next/script";
import "@calcom/embed-core/src/embed-iframe";
import { useEmbedUiConfig } from "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { trpc } from "@calcom/trpc/react";
import type { AppProps } from "@lib/app-providers";
import AppProviders from "@lib/app-providers";
import { seoConfig } from "@lib/config/next-seo.config";
import I18nLanguageHandler from "@components/I18nLanguageHandler";
import "../styles/globals.css";
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
const calFont = localFont({
src: "../fonts/CalSans-SemiBold.woff2",
variable: "--font-cal",
preload: true,
display: "swap",
});
function MyApp(props: AppProps) {
const { Component, pageProps, err, router } = props;
let pageStatus = "200";
const { cssVarsPerTheme } = useEmbedUiConfig();
const cssVarsStyle = [];
if (cssVarsPerTheme) {
for (const [themeName, cssVars] of Object.entries(cssVarsPerTheme)) {
cssVarsStyle.push(`.${themeName} {`);
for (const [cssVarName, value] of Object.entries(cssVars)) {
cssVarsStyle.push(`--${cssVarName}: ${value};`);
}
cssVarsStyle.push(`}`);
}
}
if (router.pathname === "/404") {
pageStatus = "404";
} else if (router.pathname === "/500") {
pageStatus = "500";
}
// On client side don't let nonce creep into DOM
// It also avoids hydration warning that says that Client has the nonce value but server has "" because browser removes nonce attributes before DOM is built
// See https://github.com/kentcdodds/nonce-hydration-issues
// Set "" only if server had it set otherwise keep it undefined because server has to match with client to avoid hydration error
const nonce = typeof window !== "undefined" ? (pageProps.nonce ? "" : undefined) : pageProps.nonce;
const providerProps = {
...props,
pageProps: {
...props.pageProps,
nonce,
},
};
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page);
return (
<AppProviders {...providerProps}>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<I18nLanguageHandler />
<Script
nonce={nonce}
id="page-status"
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
/>
<style jsx global>{`
:root {
--font-inter: ${interFont.style.fontFamily};
--font-cal: ${calFont.style.fontFamily};
}
`}</style>
<style jsx global>
{`
${cssVarsStyle.join("")}
`}
</style>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</Head>
{getLayout(
Component.requiresLicense ? (
<LicenseRequired>
<Component {...pageProps} err={err} />
</LicenseRequired>
) : (
<Component {...pageProps} err={err} />
),
router
)}
</AppProviders>
);
const { Component, pageProps } = props;
if (Component.PageWrapper !== undefined) return Component.PageWrapper(props);
return <Component {...pageProps} />;
}
export default trpc.withTRPC(MyApp);
+12 -9
View File
@@ -1,22 +1,27 @@
import type { NextPageContext } from "next";
import type { DocumentContext, DocumentProps } from "next/document";
import Document, { Head, Html, Main, NextScript } from "next/document";
import Script from "next/script";
import { z } from "zod";
import { getDirFromLang } from "@calcom/lib/i18n";
import { csp } from "@lib/csp";
type Props = Record<string, unknown> & DocumentProps;
function setHeader(ctx: NextPageContext, name: string, value: string) {
try {
ctx.res?.setHeader(name, value);
} catch (e) {
// Getting "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when revalidate calendar chache
console.log(`Error setting header ${name}=${value} for ${ctx.asPath || "unknown asPath"}`, e);
}
}
class MyDocument extends Document<Props> {
static async getInitialProps(ctx: DocumentContext) {
const { nonce } = csp(ctx.req || null, ctx.res || null);
if (!process.env.CSP_POLICY) {
ctx.res?.setHeader("x-csp", "not-opted-in");
setHeader(ctx, "x-csp", "not-opted-in");
} else if (!ctx.res?.getHeader("x-csp")) {
// If x-csp not set by gSSP, then it's initialPropsOnly
ctx.res?.setHeader("x-csp", "initialPropsOnly");
setHeader(ctx, "x-csp", "initialPropsOnly");
}
const asPath = ctx.asPath || "";
// Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only
@@ -31,9 +36,8 @@ class MyDocument extends Document<Props> {
const { isEmbed } = this.props;
const nonceParsed = z.string().safeParse(this.props.nonce);
const nonce = nonceParsed.success ? nonceParsed.data : "";
const dir = getDirFromLang(locale);
return (
<Html lang={locale} dir={dir}>
<Html lang={locale}>
<Head nonce={nonce}>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
@@ -42,7 +46,6 @@ class MyDocument extends Document<Props> {
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
<meta name="msapplication-TileColor" content="#ff0000" />
<meta name="theme-color" content="var(--cal-bg)" />
<Script src="/embed-init-iframe.js" strategy="beforeInteractive" />
</Head>
<body
+4 -4
View File
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return;
}
const user = await prisma.user.findUnique({
const userWithCredentials = await prisma.user.findUnique({
where: {
id: session.user.id,
},
@@ -25,11 +25,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
selectedCalendars: true,
},
});
if (!user) {
if (!userWithCredentials) {
res.status(401).json({ message: "Not authenticated" });
return;
}
const { credentials, ...user } = userWithCredentials;
if (req.method === "POST") {
await prisma.selectedCalendar.upsert({
@@ -80,7 +80,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
// get user's credentials + their connected integrations
const calendarCredentials = getCalendarCredentials(user.credentials);
const calendarCredentials = getCalendarCredentials(credentials);
// get all the connected integrations' calendars (from third party)
const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, user.selectedCalendars);
const calendars = connectedCalendars.flatMap((c) => c.calendars).filter(notEmpty);
+28
View File
@@ -0,0 +1,28 @@
import type { NextApiRequest, NextApiResponse } from "next";
import crypto from "node:crypto";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { defaultHandler } from "@calcom/lib/server";
async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getServerSession({ req, res });
const secret = process.env.INTERCOM_SECRET;
if (!session) {
return res.status(401).json({ message: "user not authenticated" });
}
if (!secret) {
return res.status(400).json({ message: "Intercom Identity Verification secret not set" });
}
const hmac = crypto.createHmac("sha256", secret);
hmac.update(String(session.user.id));
const hash = hmac.digest("hex");
return res.status(200).json({ hash });
}
export default defaultHandler({
GET: Promise.resolve({ default: handler }),
});
+80
View File
@@ -0,0 +1,80 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { IS_SELF_HOSTED, LOGO, LOGO_ICON, WEBAPP_URL } from "@calcom/lib/constants";
function removePort(url: string) {
return url.replace(/:\d+$/, "");
}
function extractSubdomainAndDomain(hostname: string) {
const hostParts = removePort(hostname).split(".");
const subdomainParts = hostParts.slice(0, hostParts.length - 2);
const domain = hostParts.slice(hostParts.length - 2).join(".");
return [subdomainParts[0], domain];
}
const logoApiSchema = z.object({
icon: z.coerce.boolean().optional(),
});
const SYSTEM_SUBDOMAINS = ["console", "app", "www"];
async function getTeamLogos(subdomain: string) {
if (
// if not cal.com
IS_SELF_HOSTED ||
// missing subdomain (empty string)
!subdomain ||
// in SYSTEM_SUBDOMAINS list
SYSTEM_SUBDOMAINS.includes(subdomain)
) {
return {
appLogo: `${WEBAPP_URL}${LOGO}`,
appIconLogo: `${WEBAPP_URL}${LOGO_ICON}`,
};
}
// load from DB
const { default: prisma } = await import("@calcom/prisma");
const team = await prisma.team.findUniqueOrThrow({
where: {
slug: subdomain,
},
select: {
appLogo: true,
appIconLogo: true,
},
});
// try to use team logos, otherwise default to LOGO/LOGO_ICON regardless
return {
appLogo: team.appLogo || `${WEBAPP_URL}${LOGO}`,
appIconLogo: team.appIconLogo || `${WEBAPP_URL}${LOGO_ICON}`,
};
}
/**
* This API endpoint is used to serve the logo associated with a team if no logo is found we serve our default logo
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { query } = req;
const parsedQuery = logoApiSchema.parse(query);
const hostname = req?.headers["host"];
if (!hostname) throw new Error("No hostname");
const domains = extractSubdomainAndDomain(hostname);
if (!domains) throw new Error("No domains");
const [subdomain] = domains;
const { appLogo, appIconLogo } = await getTeamLogos(subdomain);
const filteredLogo = parsedQuery?.icon ? appIconLogo : appLogo;
const response = await fetch(filteredLogo);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
res.setHeader("Content-Type", response.headers.get("content-type") as string);
res.setHeader("Cache-Control", "s-maxage=86400");
res.send(buffer);
}
@@ -54,7 +54,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
user: {
select: {
id: true,
credentials: true,
timeZone: true,
email: true,
name: true,
+6 -1
View File
@@ -9,13 +9,16 @@ import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"
import type { AppProps } from "@lib/app-providers";
import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
type AppPageType = {
getServerSideProps: AppGetServerSideProps;
// A component than can accept any properties
// eslint-disable-next-line @typescript-eslint/no-explicit-any
default: ((props: any) => JSX.Element) & Pick<AppProps["Component"], "isBookingPage" | "getLayout">;
default: ((props: any) => JSX.Element) &
Pick<AppProps["Component"], "isBookingPage" | "getLayout" | "PageWrapper">;
};
type Found = {
@@ -94,6 +97,8 @@ AppPage.getLayout = (page, router) => {
return route.Component.getLayout(page, router);
};
AppPage.PageWrapper = PageWrapper;
export default AppPage;
export async function getServerSideProps(
+3
View File
@@ -10,6 +10,7 @@ import prisma from "@calcom/prisma";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import App from "@components/apps/App";
const md = new MarkdownIt("default", { html: true, breaks: true });
@@ -122,4 +123,6 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
};
};
SingleAppPage.PageWrapper = PageWrapper;
export default SingleAppPage;
+4
View File
@@ -5,6 +5,8 @@ import { useRouter } from "next/router";
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
import PageWrapper from "@components/PageWrapper";
export default function SetupInformation(props: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
const slug = router.query.slug as string;
@@ -26,6 +28,8 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
return <AppSetupPage slug={slug} {...props} />;
}
SetupInformation.PageWrapper = PageWrapper;
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [],
@@ -9,6 +9,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { AppCard, SkeletonText } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t, isLocaleReady } = useLocale();
const router = useRouter();
@@ -47,6 +49,8 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
);
}
Apps.PageWrapper = PageWrapper;
export const getStaticPaths = async () => {
const paths = Object.keys(AppCategories);
+5 -1
View File
@@ -7,11 +7,13 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SkeletonText } from "@calcom/ui";
import { ArrowLeft, ArrowRight } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
const { t, isLocaleReady } = useLocale();
return (
<Shell isPublic large>
<Shell isPublic large hideHeadingOnMobile>
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
<Link
href="/apps"
@@ -43,6 +45,8 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
);
}
Apps.PageWrapper = PageWrapper;
export const getStaticProps = async () => {
const appStore = await getAppRegistry();
const categories = appStore.reduce((c, app) => {
+3
View File
@@ -12,6 +12,7 @@ import type { HorizontalTabItemProps } from "@calcom/ui";
import { AllApps, AppStoreCategories, HorizontalTabs, TextField, PopularAppsSlider } from "@calcom/ui";
import { Search } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import AppsLayout from "@components/apps/layouts/AppsLayout";
import { ssrInit } from "@server/lib/ssr";
@@ -85,6 +86,8 @@ export default function Apps({ categories, appStore }: inferSSRProps<typeof getS
);
}
Apps.PageWrapper = PageWrapper;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, res } = context;
@@ -44,6 +44,7 @@ import {
import { QueryCell } from "@lib/QueryCell";
import AppListCard from "@components/AppListCard";
import PageWrapper from "@components/PageWrapper";
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout";
@@ -372,3 +373,5 @@ export async function getServerSideProps(ctx: AppGetServerSidePropsContext) {
},
};
}
InstalledApps.PageWrapper = PageWrapper;
+3
View File
@@ -7,6 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, SkeletonText } from "@calcom/ui";
import { X } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import AuthContainer from "@components/ui/AuthContainer";
import { ssgInit } from "@server/lib/ssg";
@@ -49,6 +50,8 @@ export default function Error() {
);
}
Error.PageWrapper = PageWrapper;
export const getStaticProps = async (context: GetStaticPropsContext) => {
const ssr = await ssgInit(context);
+2 -1
View File
@@ -12,6 +12,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { Button, TextField } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
import AuthContainer from "@components/ui/AuthContainer";
type Props = {
@@ -174,7 +175,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
}
Page.isThemeSupported = false;
Page.PageWrapper = PageWrapper;
export async function getServerSideProps(context: GetServerSidePropsContext) {
const id = context.params?.id as string;
@@ -11,6 +11,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, EmailField } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
import AuthContainer from "@components/ui/AuthContainer";
export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
@@ -144,6 +145,7 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
}
ForgotPassword.isThemeSupported = false;
ForgotPassword.PageWrapper = PageWrapper;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, res } = context;
+10 -8
View File
@@ -26,6 +26,7 @@ import type { WithNonceProps } from "@lib/withNonce";
import withNonce from "@lib/withNonce";
import AddToHomescreen from "@components/AddToHomescreen";
import PageWrapper from "@components/PageWrapper";
import TwoFactor from "@components/auth/TwoFactor";
import AuthContainer from "@components/ui/AuthContainer";
@@ -164,14 +165,6 @@ export default function Login({
{...register("email")}
/>
<div className="relative">
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
<Link
href="/auth/forgot-password"
tabIndex={-1}
className="text-default text-sm font-medium">
{t("forgot")}
</Link>
</div>
<PasswordField
id="password"
autoComplete="off"
@@ -179,6 +172,14 @@ export default function Login({
className="mb-0"
{...register("password")}
/>
<div className="absolute -top-1.5 ltr:right-0 rtl:left-0">
<Link
href="/auth/forgot-password"
tabIndex={-1}
className="text-default text-sm font-medium">
{t("forgot")}
</Link>
</div>
</div>
</div>
@@ -302,5 +303,6 @@ const _getServerSideProps = async function getServerSideProps(context: GetServer
};
Login.isThemeSupported = false;
Login.PageWrapper = PageWrapper;
export const getServerSideProps = withNonce(_getServerSideProps);
+2 -1
View File
@@ -10,6 +10,7 @@ import { Check } from "@calcom/ui/components/icon";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import AuthContainer from "@components/ui/AuthContainer";
import { ssrInit } from "@server/lib/ssr";
@@ -51,7 +52,7 @@ export function Logout(props: Props) {
}
Logout.isThemeSupported = false;
Logout.PageWrapper = PageWrapper;
export default Logout;
export async function getServerSideProps(context: GetServerSidePropsContext) {
+3
View File
@@ -2,6 +2,8 @@ import { signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import PageWrapper from "@components/PageWrapper";
// To handle the IdP initiated login flow callback
export default function Page() {
const router = useRouter();
@@ -21,3 +23,4 @@ export default function Page() {
return null;
}
Page.PageWrapper = PageWrapper;
+2 -1
View File
@@ -11,6 +11,7 @@ import prisma from "@calcom/prisma";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Meta, WizardForm } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
import { AdminUserContainer as AdminUser } from "@components/setup/AdminUser";
import ChooseLicense from "@components/setup/ChooseLicense";
import EnterpriseLicense from "@components/setup/EnterpriseLicense";
@@ -136,7 +137,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
}
Setup.isThemeSupported = false;
Setup.PageWrapper = PageWrapper;
export default Setup;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
+4
View File
@@ -4,6 +4,8 @@ import { getProviders, signIn, getCsrfToken } from "next-auth/react";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { Button } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
type Provider = {
name: string;
id: string;
@@ -23,6 +25,8 @@ function signin({ providers }: { providers: Provider[] }) {
);
}
signin.PageWrapper = PageWrapper;
export default signin;
export async function getServerSideProps(context: GetServerSidePropsContext) {
+4
View File
@@ -19,6 +19,8 @@ import prisma from "@calcom/prisma";
import { asStringOrNull } from "@lib/asStringOrNull";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
export type SSOProviderPageProps = inferSSRProps<typeof getServerSideProps>;
@@ -49,6 +51,8 @@ export default function Provider(props: SSOProviderPageProps) {
return null;
}
Provider.PageWrapper = PageWrapper;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
// get query params and typecast them to string
// (would be even better to assert them instead of typecasting)
+20 -12
View File
@@ -1,33 +1,41 @@
import { signIn } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
// This page is used to initiate the SAML authentication flow by redirecting to the SAML provider.
// Accessible only on self-hosted Cal.com instances.
export default function Page({ samlTenantID, samlProductID }: inferSSRProps<typeof getServerSideProps>) {
const router = useRouter();
if (HOSTED_CAL_FEATURES) {
router.push("/auth/login");
return;
}
useEffect(() => {
if (HOSTED_CAL_FEATURES) {
router.push("/auth/login");
}
}, []);
// Initiate SAML authentication flow
signIn(
"saml",
{
callbackUrl: "/",
},
{ tenant: samlTenantID, product: samlProductID }
);
useEffect(() => {
// Initiate SAML authentication flow
signIn(
"saml",
{
callbackUrl: "/",
},
{ tenant: samlTenantID, product: samlProductID }
);
}, []);
return null;
}
Page.PageWrapper = PageWrapper;
export async function getServerSideProps() {
return {
props: {
+3
View File
@@ -11,6 +11,7 @@ import { trpc } from "@calcom/trpc/react";
import { Button, showToast } from "@calcom/ui";
import Loader from "@components/Loader";
import PageWrapper from "@components/PageWrapper";
async function sendVerificationLogin(email: string, username: string) {
await signIn("email", {
@@ -177,3 +178,5 @@ export default function Verify() {
</div>
);
}
Verify.PageWrapper = PageWrapper;
+60 -9
View File
@@ -1,4 +1,5 @@
import { useRouter } from "next/router";
import { useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
@@ -24,13 +25,19 @@ import {
Tooltip,
Dialog,
DialogTrigger,
DropdownMenuSeparator,
Dropdown,
DropdownMenuContent,
DropdownItem,
DropdownMenuTrigger,
ConfirmationDialogContent,
VerticalDivider,
} from "@calcom/ui";
import { Info, Plus, Trash } from "@calcom/ui/components/icon";
import { Info, Plus, Trash, MoreHorizontal } from "@calcom/ui/components/icon";
import { HttpError } from "@lib/core/http/error";
import PageWrapper from "@components/PageWrapper";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
import EditableHeading from "@components/ui/EditableHeading";
@@ -96,6 +103,7 @@ export default function Availability() {
const { fromEventType } = router.query;
const { timeFormat } = me.data || { timeFormat: null };
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const { data: schedule, isLoading } = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId },
{
@@ -181,11 +189,8 @@ export default function Availability() {
}
CTA={
<div className="flex items-center justify-end">
<div className="sm:hover:bg-subtle flex items-center rounded-md px-2">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
className="mt-2 hidden cursor-pointer self-center pr-2 sm:inline">
<div className="hidden items-center rounded-md px-2 sm:flex sm:hover:bg-gray-100">
<Skeleton as={Label} htmlFor="hiddenSwitch" className="mt-2 cursor-pointer self-center pr-2 ">
{t("set_to_default")}
</Skeleton>
<Switch
@@ -198,7 +203,7 @@ export default function Availability() {
/>
</div>
<VerticalDivider />
<VerticalDivider className="hidden sm:inline" />
<Dialog>
<DialogTrigger asChild>
<Button
@@ -206,6 +211,7 @@ export default function Availability() {
variant="icon"
color="destructive"
aria-label={t("delete")}
className="hidden sm:inline"
disabled={schedule?.isLastSchedule}
tooltip={t("requires_at_least_one_schedule")}
/>
@@ -222,8 +228,51 @@ export default function Availability() {
{t("delete_schedule_description")}
</ConfirmationDialogContent>
</Dialog>
<VerticalDivider />
<VerticalDivider className="hidden sm:inline" />
<Dropdown>
<DropdownMenuTrigger asChild>
<Button className="sm:hidden" StartIcon={MoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent style={{ minWidth: "200px" }}>
<DropdownItem
type="button"
color="destructive"
StartIcon={Trash}
onClick={() => setDeleteDialogOpen(true)}>
{t("delete")}
</DropdownItem>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
isLoading={deleteMutation.isLoading}
variety="danger"
title={t("delete_schedule")}
confirmBtnText={t("delete")}
loadingText={t("delete")}
onConfirm={() => {
schedule !== undefined && deleteMutation.mutate({ scheduleId: schedule.id });
}}>
{t("delete_schedule_description")}
</ConfirmationDialogContent>
</Dialog>
<DropdownMenuSeparator />
<div className="flex h-9 flex-row items-center justify-between py-2 px-4 hover:bg-gray-100">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
className="mt-2 cursor-pointer self-center pr-2 sm:inline">
{t("set_to_default")}
</Skeleton>
<Switch
id="hiddenSwitch"
disabled={isLoading || schedule?.isDefault}
checked={form.watch("isDefault")}
onCheckedChange={(e) => {
form.setValue("isDefault", e);
}}
/>
</div>
</DropdownMenuContent>
</Dropdown>
<div className="border-default border-l-2" />
<Button className="ml-4 lg:ml-0" type="submit" form="availability-form">
@@ -301,3 +350,5 @@ export default function Availability() {
</Shell>
);
}
Availability.PageWrapper = PageWrapper;
+8 -1
View File
@@ -11,6 +11,7 @@ import { Clock } from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
import PageWrapper from "@components/PageWrapper";
import SkeletonLoader from "@components/availability/SkeletonLoader";
export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) {
@@ -110,9 +111,15 @@ export default function AvailabilityPage() {
const { t } = useLocale();
return (
<div>
<Shell heading={t("availability")} subtitle={t("configure_availability")} CTA={<NewScheduleButton />}>
<Shell
heading={t("availability")}
hideHeadingOnMobile
subtitle={t("configure_availability")}
CTA={<NewScheduleButton />}>
<WithQuery success={({ data }) => <AvailabilityList {...data} />} customLoader={<SkeletonLoader />} />
</Shell>
</div>
);
}
AvailabilityPage.PageWrapper = PageWrapper;
+4 -1
View File
@@ -7,6 +7,8 @@ import { SkeletonText } from "@calcom/ui";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import PageWrapper from "@components/PageWrapper";
type User = RouterOutputs["viewer"]["me"];
export interface IBusySlot {
@@ -120,12 +122,13 @@ export default function Troubleshoot() {
const { t } = useLocale();
return (
<div>
<Shell heading={t("troubleshoot")} subtitle={t("troubleshoot_description")}>
<Shell heading={t("troubleshoot")} hideHeadingOnMobile subtitle={t("troubleshoot_description")}>
{!isLoading && data && <AvailabilityView user={data} />}
</Shell>
</div>
);
}
Troubleshoot.PageWrapper = PageWrapper;
function convertMinsToHrsMins(mins: number) {
const h = Math.floor(mins / 60);
+63 -13
View File
@@ -23,6 +23,7 @@ import {
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import {
SystemField,
getBookingFieldsWithSystemFields,
@@ -53,6 +54,7 @@ import { X, ExternalLink, ChevronLeft, Check, Calendar } from "@calcom/ui/compon
import { timeZone } from "@lib/clock";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import CancelBooking from "@components/booking/CancelBooking";
import EventReservationSchema from "@components/schemas/EventReservationSchema";
@@ -105,10 +107,10 @@ export default function Success(props: SuccessProps) {
seatReferenceUid,
} = querySchema.parse(router.query);
const tz =
(isSuccessBookingPage
? props.bookingInfo.attendees.find((attendee) => attendee.email === email)?.timeZone
: props.bookingInfo.eventType?.timeZone || props.bookingInfo.user?.timeZone) || timeZone();
const attendeeTimeZone = props?.bookingInfo?.attendees.find(
(attendee) => attendee.email === email
)?.timeZone;
const tz = isSuccessBookingPage && attendeeTimeZone ? attendeeTimeZone : props.tz ? props.tz : timeZone();
const location = props.bookingInfo.location as ReturnType<typeof getEventLocationValue>;
@@ -708,6 +710,7 @@ export default function Success(props: SuccessProps) {
}
Success.isBookingPage = true;
Success.PageWrapper = PageWrapper;
type RecurringBookingsProps = {
eventType: SuccessProps["eventType"];
@@ -888,16 +891,31 @@ const getEventTypesFromDB = async (id: number) => {
};
};
const handleSeatsEventTypeOnBooking = (
const handleSeatsEventTypeOnBooking = async (
eventType: {
seatsPerTimeSlot?: number | null;
seatsShowAttendees: boolean | null;
[x: string | number | symbol]: unknown;
},
bookingInfo: Partial<
Prisma.BookingGetPayload<{ include: { attendees: { select: { name: true; email: true } } } }>
Prisma.BookingGetPayload<{
include: {
attendees: { select: { name: true; email: true } };
seatsReferences: { select: { referenceUid: true } };
user: {
select: {
id: true;
name: true;
email: true;
username: true;
timeZone: true;
};
};
};
}>
>,
email: string
seatReferenceUid?: string,
userId?: number
) => {
if (eventType?.seatsPerTimeSlot !== null) {
// @TODO: right now bookings with seats doesn't save every description that its entered by every user
@@ -905,21 +923,52 @@ const handleSeatsEventTypeOnBooking = (
} else {
return;
}
// @TODO: If handling teams, we need to do more check ups for this.
if (bookingInfo?.user?.id === userId) {
return;
}
if (!eventType.seatsShowAttendees) {
const attendee = bookingInfo?.attendees?.find((a) => {
return a.email === email;
const seatAttendee = await prisma.bookingSeat.findFirst({
where: {
referenceUid: seatReferenceUid,
},
include: {
attendee: {
select: {
name: true,
email: true,
},
},
},
});
bookingInfo["attendees"] = attendee ? [attendee] : [];
if (seatAttendee) {
const attendee = bookingInfo?.attendees?.find((a) => {
return a.email === seatAttendee.attendee?.email;
});
bookingInfo["attendees"] = attendee ? [attendee] : [];
} else {
bookingInfo["attendees"] = [];
}
}
return bookingInfo;
};
export async function getServerSideProps(context: GetServerSidePropsContext) {
const ssr = await ssrInit(context);
const session = await getServerSession(context);
let tz: string | null = null;
if (session) {
const user = await ssr.viewer.me.fetch();
tz = user.timeZone;
}
const parsedQuery = querySchema.safeParse(context.query);
if (!parsedQuery.success) return { notFound: true };
const { uid, email, eventTypeSlug, cancel, isSuccessBookingPage } = parsedQuery.data;
const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data;
const bookingInfoRaw = await prisma.booking.findFirst({
where: {
@@ -1025,8 +1074,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
slug: eventType.team?.slug || eventType.users[0]?.username || null,
};
if (bookingInfo !== null && email && eventType.seatsPerTimeSlot) {
handleSeatsEventTypeOnBooking(eventType, bookingInfo, email);
if (bookingInfo !== null && eventType.seatsPerTimeSlot) {
await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id);
}
const payment = await prisma.payment.findFirst({
@@ -1049,6 +1098,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
dynamicEventName: bookingInfo?.eventType?.eventName || "",
bookingInfo,
paymentStatus: payment,
...(tz && { tz }),
},
};
}
+3
View File
@@ -16,6 +16,7 @@ import { Calendar } from "@calcom/ui/components/icon";
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
import PageWrapper from "@components/PageWrapper";
import BookingListItem from "@components/booking/BookingListItem";
import SkeletonLoader from "@components/booking/SkeletonLoader";
@@ -194,6 +195,8 @@ export default function Bookings() {
);
}
Bookings.PageWrapper = PageWrapper;
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const ssg = await ssgInit(ctx);
+2
View File
@@ -14,6 +14,7 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import PageWrapper from "@components/PageWrapper";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
import { ssrInit } from "@server/lib/ssr";
@@ -25,6 +26,7 @@ export default function Type(props: DynamicAvailabilityPageProps) {
}
Type.isBookingPage = true;
Type.PageWrapper = PageWrapper;
const querySchema = z.object({
link: z.string().optional().default(""),
+2
View File
@@ -9,6 +9,7 @@ import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } fr
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import BookingPage from "@components/booking/pages/BookingPage";
import { ssrInit } from "@server/lib/ssr";
@@ -20,6 +21,7 @@ export default function Book(props: HashLinkPageProps) {
}
Book.isBookingPage = true;
Book.PageWrapper = PageWrapper;
export async function getServerSideProps(context: GetServerSidePropsContext) {
const ssr = await ssrInit(context);
+70 -11
View File
@@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import type { PeriodType } from "@prisma/client";
import type { SchedulingType } from "@prisma/client";
import type { PeriodType, SchedulingType } from "@prisma/client";
import type { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import { useEffect, useState } from "react";
@@ -18,10 +17,10 @@ import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
import { useTelemetry, telemetryEventTypes } from "@calcom/lib/telemetry";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { Prisma } from "@calcom/prisma/client";
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import type { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { IntervalLimit, RecurringEvent } from "@calcom/types/Calendar";
@@ -30,9 +29,11 @@ import { ConfirmationDialogContent, Dialog, Form, showToast } from "@calcom/ui";
import { asStringOrThrow } from "@lib/asStringOrNull";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
// These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings
import { EventAdvancedTab } from "@components/eventtype/EventAdvancedTab";
import { EventAppsTab } from "@components/eventtype/EventAppsTab";
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
import { EventAvailabilityTab } from "@components/eventtype/EventAvailabilityTab";
import { EventLimitsTab } from "@components/eventtype/EventLimitsTab";
import { EventRecurringTab } from "@components/eventtype/EventRecurringTab";
@@ -93,6 +94,7 @@ export type FormValues = {
children: ChildrenEventType[];
hosts: { userId: number; isFixed: boolean }[];
bookingFields: z.infer<typeof eventTypeBookingFields>;
availability?: AvailabilityOption;
};
export type CustomInputParsed = typeof customInputSchema._output;
@@ -261,6 +263,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
}, [defaultValues]);
const appsMetadata = formMethods.getValues("metadata")?.apps;
const availability = formMethods.watch("availability");
const numberOfInstalledApps = eventTypeApps?.filter((app) => app.isInstalled).length || 0;
let numberOfActiveApps = 0;
@@ -377,6 +380,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
enabledWorkflowsNumber={eventType.workflows.length}
eventType={eventType}
team={team}
availability={availability}
isUpdateMutationLoading={updateMutation.isLoading}
formMethods={formMethods}
disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
@@ -384,13 +388,66 @@ const EventTypePage = (props: EventTypeSetupProps) => {
<Form
form={formMethods}
id="event-type-form"
handleSubmit={async (values: FormValues) => {
if (!values.children.length) return handleSubmit(values);
const existingSlugEventTypes = values.children.filter((ch) =>
ch.owner.eventTypeSlugs.includes(slug)
);
if (!existingSlugEventTypes.length) return handleSubmit(values);
setSlugExistsChildrenDialogOpen(existingSlugEventTypes);
handleSubmit={async (values) => {
const {
periodDates,
periodCountCalendarDays,
beforeBufferTime,
afterBufferTime,
seatsPerTimeSlot,
seatsShowAttendees,
bookingLimits,
durationLimits,
recurringEvent,
locations,
metadata,
customInputs,
// We don't need to send send these values to the backend
// eslint-disable-next-line @typescript-eslint/no-unused-vars
seatsPerTimeSlotEnabled,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
minimumBookingNoticeInDurationType,
availability,
...input
} = values;
if (bookingLimits) {
const isValid = validateIntervalLimitOrder(bookingLimits);
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
}
if (durationLimits) {
const isValid = validateIntervalLimitOrder(durationLimits);
if (!isValid) throw new Error(t("event_setup_duration_limits_error"));
}
if (metadata?.multipleDuration !== undefined) {
if (metadata?.multipleDuration.length < 1) {
throw new Error(t("event_setup_multiple_duration_error"));
} else {
if (!input.length && !metadata?.multipleDuration?.includes(input.length)) {
throw new Error(t("event_setup_multiple_duration_default_error"));
}
}
}
updateMutation.mutate({
...input,
locations,
recurringEvent,
periodStartDate: periodDates.startDate,
periodEndDate: periodDates.endDate,
periodCountCalendarDays: periodCountCalendarDays === "1",
id: eventType.id,
beforeEventBuffer: beforeBufferTime,
afterEventBuffer: afterBufferTime,
bookingLimits,
durationLimits,
seatsPerTimeSlot,
seatsShowAttendees,
metadata,
customInputs,
});
}}>
<div ref={animationParentRef}>{tabMap[tabName]}</div>
</Form>
@@ -477,4 +534,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
};
};
EventTypePageWrapper.PageWrapper = PageWrapper;
export default EventTypePageWrapper;
+13 -1
View File
@@ -8,6 +8,7 @@ import type { FC } from "react";
import { useEffect, useState, memo } from "react";
import { z } from "zod";
import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
@@ -61,6 +62,7 @@ import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
import { EmbedButton, EmbedDialog } from "@components/Embed";
import PageWrapper from "@components/PageWrapper";
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
@@ -755,9 +757,16 @@ const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer);
const EventTypesPage = () => {
const { t } = useLocale();
const router = useRouter();
const { open } = useIntercom();
const { query } = router;
const isMobile = useMediaQuery("(max-width: 768px)");
useEffect(() => {
if (query?.openIntercom && query?.openIntercom === "true") {
open();
}
}, []);
return (
<div>
<HeadSeo
@@ -767,6 +776,7 @@ const EventTypesPage = () => {
<Shell
withoutSeo
heading={t("event_types_page_title")}
hideHeadingOnMobile
subtitle={t("event_types_page_subtitle")}
CTA={<CTA />}>
<WithQuery
@@ -817,4 +827,6 @@ const EventTypesPage = () => {
);
};
EventTypesPage.PageWrapper = PageWrapper;
export default EventTypesPage;
@@ -13,6 +13,7 @@ import { Button, StepCard, Steps } from "@calcom/ui";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars";
import { SetupAvailability } from "@components/getting-started/steps-views/SetupAvailability";
import UserProfile from "@components/getting-started/steps-views/UserProfile";
@@ -203,5 +204,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
};
OnboardingPage.isThemeSupported = false;
OnboardingPage.PageWrapper = PageWrapper;
export default OnboardingPage;
+5 -1
View File
@@ -17,6 +17,8 @@ import { trpc } from "@calcom/trpc";
import { Button, ButtonGroup } from "@calcom/ui";
import { RefreshCcw, UserPlus, Users } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
const Heading = () => {
const { t } = useLocale();
@@ -53,7 +55,7 @@ export default function InsightsPage() {
return (
<div>
<Shell>
<Shell hideHeadingOnMobile>
<UpgradeTip
title={t("make_informed_decisions")}
description={t("make_informed_decisions_description")}
@@ -111,6 +113,8 @@ export default function InsightsPage() {
);
}
InsightsPage.PageWrapper = PageWrapper;
// If feature flag is disabled, return not found on getServerSideProps
export const getServerSideProps = async () => {
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
+4
View File
@@ -4,6 +4,8 @@ import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
export default function MaintenancePage() {
const { t, isLocaleReady } = useLocale();
if (!isLocaleReady) return null;
@@ -25,3 +27,5 @@ export default function MaintenancePage() {
</div>
);
}
MaintenancePage.PageWrapper = PageWrapper;
+4 -1
View File
@@ -1,10 +1,12 @@
import Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import PageWrapper from "@components/PageWrapper";
export default function MorePage() {
const { t } = useLocale();
return (
<Shell>
<Shell hideHeadingOnMobile>
<div className="max-w-screen-lg">
<MobileNavigationMoreItems />
<p className="text-subtle mt-6 text-xs leading-tight md:hidden">{t("more_page_footer")}</p>
@@ -12,3 +14,4 @@ export default function MorePage() {
</Shell>
);
}
MorePage.PageWrapper = PageWrapper;
+3 -1
View File
@@ -2,8 +2,10 @@ import PaymentPage from "@calcom/features/ee/payments/components/PaymentPage";
import { getServerSideProps } from "@calcom/features/ee/payments/pages/payment";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import PageWrapper from "@components/PageWrapper";
export default function Payment(props: inferSSRProps<typeof getServerSideProps>) {
return <PaymentPage {...props} />;
}
Payment.PageWrapper = PageWrapper;
export { getServerSideProps };
@@ -2,6 +2,7 @@ import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Meta } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
import { getLayout } from "@components/auth/layouts/AdminLayout";
function AdminAppsView() {
@@ -15,5 +16,6 @@ function AdminAppsView() {
}
AdminAppsView.getLayout = getLayout;
AdminAppsView.PageWrapper = PageWrapper;
export default AdminAppsView;
+2
View File
@@ -1,9 +1,11 @@
import { FlagListingView } from "@calcom/features/flags/pages/flag-listing-view";
import PageWrapper from "@components/PageWrapper";
import { getLayout } from "@components/auth/layouts/AdminLayout";
const FlagsPage = () => <FlagListingView />;
FlagsPage.getLayout = getLayout;
FlagsPage.PageWrapper = PageWrapper;
export default FlagsPage;
@@ -4,6 +4,7 @@ import { useRef } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Meta, TextField } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
import { getLayout } from "@components/auth/layouts/AdminLayout";
function AdminView() {
@@ -37,5 +38,6 @@ function AdminView() {
}
AdminView.getLayout = getLayout;
AdminView.PageWrapper = PageWrapper;
export default AdminView;
+2
View File
@@ -1,5 +1,6 @@
import { Meta } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
import { getLayout } from "@components/auth/layouts/AdminLayout";
function AdminAppsView() {
@@ -12,5 +13,6 @@ function AdminAppsView() {
}
AdminAppsView.getLayout = getLayout;
AdminAppsView.PageWrapper = PageWrapper;
export default AdminAppsView;
@@ -1 +1,9 @@
export { default } from "@calcom/features/ee/users/pages/users-edit-view";
import UsersEditView from "@calcom/features/ee/users/pages/users-edit-view";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = UsersEditView as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
+9 -1
View File
@@ -1 +1,9 @@
export { default } from "@calcom/features/ee/users/pages/users-add-view";
import UsersAddView from "@calcom/features/ee/users/pages/users-add-view";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = UsersAddView as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1 +1,9 @@
export { default } from "@calcom/features/ee/users/pages/users-listing-view";
import UsersListingView from "@calcom/features/ee/users/pages/users-listing-view";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = UsersListingView as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
+5 -2
View File
@@ -8,6 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Meta } from "@calcom/ui";
import { ExternalLink } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
interface CtaRowProps {
title: string;
description: string;
@@ -37,8 +39,8 @@ const BillingView = () => {
const returnTo = router.asPath;
const billingHref = `/api/integrations/stripepayment/portal?returnTo=${WEBAPP_URL}${returnTo}`;
const onContactSupportClick = () => {
open();
const onContactSupportClick = async () => {
await open();
};
return (
@@ -64,5 +66,6 @@ const BillingView = () => {
};
BillingView.getLayout = getLayout;
BillingView.PageWrapper = PageWrapper;
export default BillingView;
@@ -1,7 +1,7 @@
import { useState } from "react";
import type { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
import LicenseRequired from "@calcom/ee/common/components/v2/LicenseRequired";
import LicenseRequired from "@calcom/ee/common/components/LicenseRequired";
import ApiKeyDialogForm from "@calcom/features/ee/api-keys/components/ApiKeyDialogForm";
import ApiKeyListItem from "@calcom/features/ee/api-keys/components/ApiKeyListItem";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
@@ -18,6 +18,8 @@ import {
} from "@calcom/ui";
import { Link as LinkIcon, Plus } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
const ApiKeysView = () => {
const { t } = useLocale();
@@ -92,5 +94,6 @@ const ApiKeysView = () => {
};
ApiKeysView.getLayout = getLayout;
ApiKeysView.PageWrapper = PageWrapper;
export default ApiKeysView;
@@ -1 +1,9 @@
export { default } from "@calcom/features/webhooks/pages/webhook-edit-view";
import WebhookEditView from "@calcom/features/webhooks/pages/webhook-edit-view";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = WebhookEditView as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1 +1,9 @@
export { default } from "@calcom/features/webhooks/pages/webhooks-view";
import WeebhooksView from "@calcom/features/webhooks/pages/webhooks-view";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = WeebhooksView as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
@@ -1 +1,9 @@
export { default } from "@calcom/features/webhooks/pages/webhook-new-view";
import WeebhookNewView from "@calcom/features/webhooks/pages/webhook-new-view";
import type { CalPageWrapper } from "@components/PageWrapper";
import PageWrapper from "@components/PageWrapper";
const Page = WeebhookNewView as CalPageWrapper;
Page.PageWrapper = PageWrapper;
export default Page;
@@ -22,6 +22,8 @@ import {
UpgradeTeamsBadge,
} from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
@@ -50,6 +52,7 @@ const AppearanceView = () => {
const utils = trpc.useContext();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
const [darkModeError, setDarkModeError] = useState(false);
const [lightModeError, setLightModeError] = useState(false);
const { isLoading: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
@@ -143,7 +146,14 @@ const AppearanceView = () => {
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
<ColorPicker
defaultValue={user.brandColor}
onChange={(value) => formMethods.setValue("brandColor", value, { shouldDirty: true })}
onChange={(value) => {
if (!checkWCAGContrastColor("#ffffff", value)) {
setLightModeError(true);
} else {
setLightModeError(false);
}
formMethods.setValue("brandColor", value, { shouldDirty: true });
}}
/>
</div>
)}
@@ -178,6 +188,14 @@ const AppearanceView = () => {
/>
</div>
) : null}
{lightModeError ? (
<div className="mt-4">
<Alert
severity="warning"
message="Light Theme color doesn't pass contrast check. We recommend you change this colour so your buttons will be more visible."
/>
</div>
) : null}
{/* TODO future PR to preview brandColors */}
{/* <Button
color="secondary"
@@ -230,5 +248,6 @@ const AppearanceView = () => {
};
AppearanceView.getLayout = getLayout;
AppearanceView.PageWrapper = PageWrapper;
export default AppearanceView;
@@ -28,6 +28,7 @@ import { Plus, Calendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
import PageWrapper from "@components/PageWrapper";
import { CalendarSwitch } from "@components/settings/CalendarSwitch";
const SkeletonLoader = () => {
@@ -228,5 +229,6 @@ const CalendarsView = () => {
};
CalendarsView.getLayout = getLayout;
CalendarsView.PageWrapper = PageWrapper;
export default CalendarsView;
@@ -27,6 +27,7 @@ import {
import { AlertCircle, MoreHorizontal, Trash, Video } from "@calcom/ui/components/icon";
import AppListCard from "@components/AppListCard";
import PageWrapper from "@components/PageWrapper";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
@@ -188,5 +189,6 @@ const ConferencingLayout = () => {
};
ConferencingLayout.getLayout = getLayout;
ConferencingLayout.PageWrapper = PageWrapper;
export default ConferencingLayout;
@@ -22,6 +22,8 @@ import {
import { withQuery } from "@lib/QueryCell";
import PageWrapper from "@components/PageWrapper";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
@@ -219,5 +221,6 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
};
GeneralQueryView.getLayout = getLayout;
GeneralQueryView.PageWrapper = PageWrapper;
export default GeneralQueryView;
@@ -40,6 +40,7 @@ import {
} from "@calcom/ui";
import { AlertTriangle, Trash2 } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import TwoFactor from "@components/auth/TwoFactor";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
@@ -319,6 +320,7 @@ const ProfileForm = ({
extraField?: React.ReactNode;
}) => {
const { t } = useLocale();
const [firstRender, setFirstRender] = useState(true);
const profileFormSchema = z.object({
username: z.string(),
@@ -384,6 +386,8 @@ const ProfileForm = ({
}}
excludedToolbarItems={["blockType"]}
disableLists
firstRender={firstRender}
setFirstRender={setFirstRender}
/>
</div>
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">
@@ -394,5 +398,6 @@ const ProfileForm = ({
};
ProfileView.getLayout = getLayout;
ProfileView.PageWrapper = PageWrapper;
export default ProfileView;
@@ -6,6 +6,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, Form, Label, Meta, showToast, Skeleton, Switch } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
const ProfileImpersonationView = () => {
@@ -85,6 +87,7 @@ const ProfileImpersonationView = () => {
};
ProfileImpersonationView.getLayout = getLayout;
ProfileImpersonationView.PageWrapper = PageWrapper;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);

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