Compare commits

...

77 Commits

Author SHA1 Message Date
zomars 5da47dd4e7 Prevent refetching day on month switch 2023-02-10 14:49:44 -07:00
zomars 418306daed Merge branch 'main' into pr/6775 2023-02-10 14:12:46 -07:00
Omar López 56f32d0be9 Fixes/trpc headers (#7045)
* Cache fixes

* Testing

* SWR breaks embed tests
2023-02-10 14:09:39 -07:00
zomars 17c374e7d4 Merge branch 'main' into pr/6775 2023-02-10 14:07:59 -07:00
zomars d5ecdaaa6e Update package.json 2023-02-10 11:43:13 -07:00
zomars e93f808980 Update [trpc].ts 2023-02-09 20:33:15 -07:00
zomars 67cae248c1 Log headers 2023-02-09 20:05:56 -07:00
zomars b732f0ec9a Update [trpc].ts 2023-02-09 17:56:21 -07:00
Efrain Rochin Aramburo d73aaf0990 Separate selectedDate slots from month slots 2023-02-09 17:42:27 -07:00
zomars bfc930a061 Caching fixes 2023-02-09 17:35:01 -07:00
Efrain Rochin Aramburo d80f7fb9c3 remove the commented selectedCalendars var, it is not necessary 2023-02-09 13:04:26 -07:00
Efrain Rochin Aramburo b572591259 use WEBAPP_URL constant instead env.NEXT_PUBLIC_WEBAPP_URL 2023-02-09 13:01:44 -07:00
Efrain Rochin Aramburo 5b219a55b3 log the json url for testing 2023-02-09 12:18:12 -07:00
zomars 33dce23415 Merge branch 'main' into pr/6775 2023-02-09 10:52:01 -07:00
zomars 6199c86337 Merge branch 'main' into pr/6775 2023-02-08 18:33:11 -07:00
Nafees Nazik f35d52fdb0 feat: return `x-vercel-ip-timezone` in headers (#6849)
* feat: add trpc to matcher and pass vercel timezone header

* feat: pass request to context

* feat: return timezone in header

* refactor: split context

* fix: remove tsignore comment

* Update [trpc].ts

---------

Co-authored-by: zomars <zomars@me.com>
2023-02-08 18:32:39 -07:00
Aaron Presley be77464f44 I18N Caching (#6823)
* Caching Logic Changes

Enabled this function to change its cache value based on incoming paths value

* Invalidate I18N Cache

Invalidating the I18N cache when a user saves changes to their General settings

* Removes deprecated useLocale location

* Overriding the default getSchedule cache to have a revalidation time of 1 second

* Update apps/web/pages/api/trpc/[trpc].ts

* Updated cache values to match the comment

---------

Co-authored-by: zomars <zomars@me.com>
2023-02-08 18:32:39 -07:00
Omar López 4bf89d68fa Update package.json 2023-02-08 18:32:39 -07:00
Alex van Andel 2ef73d3668 Feature/maintenance mode (#6930)
* Implement maintenance mode with Vercel Edge Config

* Error log is spam during development/ added \n in .env.example

* Exclude _next, /api for /maintenance page

* Re-instate previous config

* rtl: begone

* Added note to explain why /auth/login covers the maintenance page.

---------

Co-authored-by: Omar López <zomars@me.com>
2023-02-08 18:32:39 -07:00
Leo Giovanetti 7195f5a576 Fixing admin wizard step done (#6954) 2023-02-08 18:32:39 -07:00
Udit Takkar 4ba61e469b fix: use typedquery hook in duplicate dialog (#6730)
* fix: use typedquery hook in duplicate dialog

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

* Update packages/features/eventtypes/components/DuplicateDialog.tsx

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Omar López <zomars@me.com>
2023-02-08 18:32:39 -07:00
Joe Au-Yeung 01c8b24698 Small UI fixes for seats & destination calendars (#6859)
* Do not add former time for events on seats

* Default display destination calendar

* Add seats badge to event type item

* Add string

* Actively watch seats enabled option for requires confirmation

* Only show former time when there is a rescheduleUid
2023-02-08 18:32:39 -07:00
alannnc ee0dbe30f6 feat/payment-service-6438-cal-767 (#6677)
* WIP paymentService

* Changes for payment Service

* Fix for stripe payment flow

* Remove logs/comments

* Refactored refund for stripe app

* Move stripe handlePayment to own lib

* Move stripe delete payments to paymentService

* lint fix

* Change handleRefundError as generic function

* remove log

* remove logs

* remove logs

* Return stripe default export to lib/server

* Fixing types

* Fix types

* Upgrades typescript

* Update yarn lock

* Typings

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712)

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712) (#6713)

* Adds deployment settings to DB (#6706)

* WIP

* Adds DeploymentTheme

* Add missing migrations

* Adds client extensions for deployment

* Cleanup

* Revert "lint fix"

This reverts commit e1a2e4a357.

* Add validation

* Revert changes removed in force push

* Removing abstract class and just leaving interface implementation

* Fix types for handlePayments

* Fix payment test appStore import

* Fix stripe metadata in event type

* Move migration to separate PR

* Revert "Move migration to separate PR"

This reverts commit 48aa64e072.

* Update packages/prisma/migrations/20230125175109_remove_type_from_payment_and_add_app_relationship/migration.sql

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

---------

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2023-02-08 18:32:39 -07:00
Deepak Prabhakara 01db31c899 Allow account linking for Google and SAML providers (#6874)
* allow account linking for self-hosted instances, both Google and SAML are verified emails

* allow account linking for Google and SSO if emails match with existing username/password account

* Tweaked find user by email since we now have multiple providers (other than credentials provider)
2023-02-08 18:32:39 -07:00
Carina Wollendorfer f7523b77a6 Fixes localisation of {EVENT_DATE} in workflows (#6907)
* translate {EVENT_DATE} variable to correct language

* fix locale for cron schedule reminder emails/sms

* fix type error

* add missing locale to attendees

* fix type error

* code clean up

* revert last commit

* using Intl for date translations

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-02-08 18:32:39 -07:00
Carina Wollendorfer ecf91b8b7c fix start icon in button (#6950)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-02-08 18:32:39 -07:00
Alex van Andel d274d7d675 Block /auth/:path, nothing else. (#6949)
* Block /auth/:path, nothing else.

* Also add /signup
2023-02-08 18:32:39 -07:00
Hariom Balhara 5bb5ac8aed Give trackingId a default value because if user doesnt interact with trackingId input it is not set (#6945) 2023-02-08 18:32:39 -07:00
Peer Richelsen 085a44928b minor timezone-select improvements (#6944)
* fixed timezone select positioning and hover

* fixed timezone select positioning and hover
2023-02-08 18:32:39 -07:00
Alex van Andel 0d62be0870 Remove _subject translations for zh-CN, needs retranslation 2023-02-08 18:32:39 -07:00
Udit Takkar df8080ce85 Email Variables Bug (#6943) 2023-02-08 18:32:39 -07:00
Udit Takkar afea3a9edc fix: add sortByLabel (#6797)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-02-08 18:32:39 -07:00
Udit Takkar 7428667212 fix: display provider name instead of url (#6914)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-02-08 18:32:39 -07:00
Ben Hybert 4533f2057c fixes the broken max size of members on teams page (#6926) 2023-02-08 18:32:39 -07:00
Joe Shajan 098221b47d fix: dropdown title in bookings page (#6924) 2023-02-08 18:32:39 -07:00
Udit Takkar acd0902fb2 fix: use destination calendar email (#6886)
* fix: use destination calendar email
to display correct primary email

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

* fix: simplify logic

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-02-08 18:32:39 -07:00
Udit Takkar e3ae0c5387 fix: color and line height of icon (#6913) 2023-02-08 18:32:39 -07:00
GitStart-Cal.com 67afb4b57c [CAL-488] Timezone selection has a weird double dropdown (#6851)
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
2023-02-08 18:32:39 -07:00
Peer Richelsen a96da8c876 added two new tips (#6915) 2023-02-08 18:32:39 -07:00
Leo Giovanetti fe0c2a522e Admin Wizard Choose License (#6574)
* Implementation

* i18n

* More i18n

* extracted i18n, needs api to get most recent price, added hint: update later

* Fixing i18n var

* Fix booking filters not working for admin (#6576)

* fix: react-select overflow issue in some modals. (#6587)

* feat: add a disable overflow prop

* feat: use the disable overflow prop

* Tailwind Merge (#6596)

* Tailwind Merge

* Fix merge classNames

* [CAL-808] /availability/single - UI issue on buttons beside time inputs (#6561)

* [CAL-808] /availability/single - UI issue on buttons beside time inputs

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

* Update packages/features/schedules/components/Schedule.tsx

* create new translation for tooltip

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* Bye bye submodules (#6585)

* WIP

* Uses ssh instead

* Update .gitignore

* Update .gitignore

* Update Makefile

* Update git-setup.sh

* Update git-setup.sh

* Replaced Makefile with bash script

* Update package.json

* fix: show button on empty string (#6601)

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

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

* fix: add delete in dropdown (#6599)

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

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

* Update README.md

* Update README.md

* Changed a neutral- classes to gray (#6603)

* Changed a neutral- classes to gray

* Changed all border-1 to border

* Update package.json

* Test fixes

* Yarn lock fixes

* Fix string equality check in git-setup.sh

* [CAL-811] Avatar icon not redirecting user back to the main page (#6586)

* Remove cursor-pointer, remove old Avatar* files

* Fixed styling for checkedSelect + some cleanup

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>

* Harsh/add member invite (#6598)

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <harsh.singh@gocomet.com>

* Regenerated lockfile without upgrade (#6610)

* fix: remove annoying outline when <Button /> clicked (#6537)

* fix: remove annoying outline when <Button /> clicked

* Delete yarn.lock

* remove 1 on 1 icon (#6609)

* removed 1-on-1 badge

* changed user to users for group events

* fix: case-sensitivity in apps path (#6552)

* fix: lowercase slug

* fix: make fallback blocking

* Fix FAB (#6611)

* feat: add LocationSelect component (#6571)

* feat: add LocationSelect component

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

* fix: type error

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

* chore: type error

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

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

* Update booking filters design  (#6543)

* Update booking filters

* Add filter on YOUR bookings

* Fix pending members showing up in list

* Reduce the avatar size to 'sm' for now

* Bugfix/dropdown menu trigger as child remove class names (#6614)

* Fix UsernameTextfield to take right height

* Remove className side-effect

* Incorrect resolution version fixed

* Converted mobile DropdownMenuTrigger styles into Button

* v2.5.3

* fix: use items-center (#6618)

* fix tooltip and modal stacking issues (#6491)

* fix tooltip and modal stacking issues

* use z-index in larger screens and less

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

* Temporary fix (#6626)

* Fix Ga4 tracking (#6630)

* generic <UpgradeScreen> component (#6594)

* first attempt of <UpgradeScreen>

* changes to icons

* reverted changes back to initial state, needs fix: teams not showing

* WIP

* Fix weird reactnode error

* Fix loading text

* added upgradeTip to routing forms

* icon colors

* create and use hook to check if user has team plan

* use useTeamPlan for upgradeTeamsBadge

* replace huge svg with compressed jpeg

* responsive fixes

* Update packages/ui/components/badge/UpgradeTeamsBadge.tsx

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Give team plan features to E2E tests

* Allow option to make a user part of team int ests

* Remove flash of paywall for team user

* Add team user for typeform tests as well

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* Removing env var to rely on db

* Restoring i18n keys, set loading moved

* Fixing tailwind-preset glob

* Wizard width fix for md+ screens

* Converting licenses options to radix radio

* Applying feedback + other tweaks

* Reverting this, not this PR related

* Unneeded code removal

* Reverting unneeded style change

* Applying feedback

* Removing licenseType

* Upgrades typescript

* Update yarn lock

* Typings

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712)

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712) (#6713)

* Adds deployment settings to DB (#6706)

* WIP

* Adds DeploymentTheme

* Add missing migrations

* Adds client extensions for deployment

* Cleanup

* Delete migration.sql

* Relying on both, env var and new model

* Restoring env example doc for backward compat

* Maximum call stack size exceeded fix?

* Revert upgrade

* Update index.ts

* Delete index.ts

* Not exposing license key, fixed radio behavior

* Covering undefined env var

* Self contained checkLicense

* Feedback

* Moar feedback

* Feedback

* Feedback

* Feedback

* Cleanup

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Nafees Nazik <84864519+G3root@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Harsh Singh <51085015+harshsinghatz@users.noreply.github.com>
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <harsh.singh@gocomet.com>
Co-authored-by: Luis Cadillo <luiscaf3r@gmail.com>
Co-authored-by: Mohammed Cherfaoui <hi@cherfaoui.dev>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-02-08 18:32:39 -07:00
alannnc e51e746ee1 add-console-vars (#6929) 2023-02-08 18:32:39 -07:00
Udit Takkar 8716146d5a fix: add req.headers (#6921)
Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-02-08 18:32:39 -07:00
Omar López 52c6ab480c v2.5.10 2023-02-08 18:32:39 -07:00
Hariom Balhara 826bf55c20 Beginning of Strict CSP Compliance (#6841)
* Add CSP Support and enable it initially for Login page

* Update README

* Make sure that CSP is not enabled if CSP_POLICY isnt set

* Add a new value for x-csp header that tells if instance has opted-in to CSP or not

* Add more src to CSP

* Fix typo in header name

* Remove duplicate headers fn

* Add https://eu.ui-avatars.com/api/

* Add CSP_POLICY to env.example
2023-02-08 18:32:39 -07:00
Nafees Nazik 0311771be0 fix: metadata not saved while creating a booking. (#6866)
* feat: add metadata to booking creation

* fix: bug
2023-02-08 18:32:39 -07:00
Peer Richelsen 5b7125b87d nit border change (#6899) 2023-02-08 18:32:38 -07:00
Peer Richelsen 6e13adeab2 theme for storybook 2023-02-08 18:32:38 -07:00
Alex van Andel f7fbfeccbd When unchecking the common schedule, schedule should be nulled (#6898) 2023-02-08 18:32:38 -07:00
Sai Deepesh 5719700d2b fix(date-overrides): alignment of edit & delete btns (#6892) 2023-02-08 18:32:38 -07:00
Alex van Andel 1d27b103ee Fixed some inconsistencies within single event type page (#6887)
* Fixed some inconsistencies within single event type page

* Fix layout shift on AvailabilityTab
2023-02-08 18:32:38 -07:00
sean-brydon 898722261b Hide button (#6895) 2023-02-08 18:32:38 -07:00
Syed Ali Shahbaz dd9f0138ca Updates heroku deployment template (#6879) 2023-02-08 18:32:38 -07:00
Udit Takkar 5805e784a6 fix: get location url from metadata (#6774)
* fix: get location url from metadata

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

* fix: replace location

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

* fix: type error

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

* fix: use zod

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-02-08 18:32:38 -07:00
Carina Wollendorfer f1b1afb2f0 fix workflows when duplicating event types (#6873) 2023-02-08 18:32:38 -07:00
Udit Takkar 6cab0e798a fix: filter booking in upcoming (#6406)
* fix: filter booking in upcoming

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

* fix: test

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

* fix: wipe-my-cal failing test

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

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-02-08 18:32:38 -07:00
Hariom Balhara 8202c2ce46 Hotfix: For old Plausible installs enabled in an EventType, give a default value (#6860)
* Add default for trackingId for old plausible installs in event-types

* Fix types
2023-02-08 18:32:38 -07:00
Peer Richelsen 19b595b93f lots of small changes by me and ciaran (#6871) 2023-02-08 18:32:38 -07:00
Alex van Andel fbc5d2c8d6 Use Calendar component view=day for drop-in replacement troubleshooter (#6869)
* Use Calendar component view=day for drop-in replacement troubleshooter

* Setting the id to undefined makes the busy time selected

* Updated event title to include title+source
2023-02-08 18:32:38 -07:00
Ben Hybert 6bfa6e1799 version 2.5.9 (#6868) 2023-02-08 18:32:38 -07:00
Nafees Nazik fdfb8a109a feat: make key unique (#6861) 2023-02-08 18:32:38 -07:00
Hariom Balhara 34da780f4a Hotfix: Event Name (#6863) (#6864) 2023-02-08 18:32:38 -07:00
Efrain Rochin Aramburo 0d39452baf refetch availability slots in a 3 seconds interval 2023-02-08 18:15:16 -07:00
Efrain Rochin Aramburo ae73393271 Remove the memory cache from getCachedResults 2023-02-08 18:12:15 -07:00
Efrain Rochin Aramburo 2a386e4188 Change revalidateCalendarCache function to @calcom/lib/server 2023-02-08 17:38:18 -07:00
Efrain Rochin Aramburo fb2412b5ce Revalidate calendar cache when user change de selected calendars- 2023-02-08 12:27:49 -07:00
Efrain Rochin Aramburo a0c81eba88 Revalidate calendar cache when user remove a calendar. 2023-02-08 12:27:10 -07:00
Efrain Rochin Aramburo 8dc5d47b54 Revalidate calendar cache when user connect new calendar. 2023-02-08 12:26:04 -07:00
Efrain Rochin Aramburo c75ac141ad Add api endpoint that revalidates the current month and 3 more ahead 2023-02-08 12:22:14 -07:00
Efrain Rochin Aramburo 12a9e46d20 Merge branch 'event_page_performance' of https://github.com/roae/cal.com into event_page_performance 2023-02-02 20:30:15 -07:00
Efraín Rochín eb94f0c8c8
Update packages/core/CalendarManager.ts
Co-authored-by: Omar López <zomars@me.com>
2023-02-02 20:29:30 -07:00
Efraín Rochín 76eaf898d0
Update apps/web/pages/[user]/calendar-cache/[month].tsx
Co-authored-by: Omar López <zomars@me.com>
2023-02-02 20:29:20 -07:00
Efraín Rochín 1f85daec5f
Update apps/web/pages/[user]/calendar-cache/[month].tsx
Co-authored-by: Omar López <zomars@me.com>
2023-02-02 20:29:00 -07:00
Efrain Rochin Aramburo 77d840552f add support to get cached data from multiple months 2023-02-02 20:25:16 -07:00
zomars d1fe6d1246 Merge branch 'main' into pr/6775 2023-02-02 14:25:35 -07:00
Efrain Rochin Aramburo 46b3ffea19 yarn.lock from main 2023-02-02 13:50:46 -07:00
Efrain Rochin Aramburo 750a4c2994 remove next-build-id dependency 2023-02-02 13:45:06 -07:00
Efrain Rochin Aramburo 902279c7da Generate SSG Page used as cache for user's third-party calendars 2023-01-28 21:25:56 -07:00
12 changed files with 202 additions and 74 deletions

View File

@ -1,7 +1,7 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventType } from "@prisma/client";
import { useRouter } from "next/router";
import { useReducer, useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useReducer, useState } from "react";
import { Toaster } from "react-hot-toast";
import { FormattedNumber, IntlProvider } from "react-intl";
import { z } from "zod";
@ -52,6 +52,7 @@ const useSlots = ({
usernameList,
timeZone,
duration,
enabled = true,
}: {
eventTypeId: number;
eventTypeSlug: string;
@ -60,6 +61,7 @@ const useSlots = ({
usernameList: string[];
timeZone?: string;
duration?: string;
enabled?: boolean;
}) => {
const { data, isLoading, isPaused } = trpc.viewer.public.slots.getSchedule.useQuery(
{
@ -72,19 +74,14 @@ const useSlots = ({
duration,
},
{
enabled: !!startTime && !!endTime,
enabled: !!startTime && !!endTime && enabled,
refetchInterval: 3000,
trpc: { context: { skipBatch: true } },
}
);
const [cachedSlots, setCachedSlots] = useState<NonNullable<typeof data>["slots"]>({});
useEffect(() => {
if (data?.slots) {
setCachedSlots((c) => ({ ...c, ...data?.slots }));
}
}, [data]);
// The very first time isPaused is set if auto-fetch is disabled, so isPaused should also be considered a loading state.
return { slots: cachedSlots, isLoading: isLoading || isPaused };
return { slots: data?.slots || {}, isLoading: isLoading || isPaused };
};
const SlotPicker = ({
@ -146,16 +143,7 @@ const SlotPicker = ({
}, [router.isReady, month, date, duration, timeZone]);
const { i18n, isLocaleReady } = useLocale();
const { slots: _1 } = useSlots({
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
usernameList: users,
startTime: selectedDate?.startOf("day"),
endTime: selectedDate?.endOf("day"),
timeZone,
duration,
});
const { slots: _2, isLoading } = useSlots({
const { slots: monthSlots, isLoading } = useSlots({
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
usernameList: users,
@ -167,8 +155,17 @@ const SlotPicker = ({
timeZone,
duration,
});
const slots = useMemo(() => ({ ..._2, ..._1 }), [_1, _2]);
const { slots: selectedDateSlots, isLoading: isLoadingSelectedDateSlots } = useSlots({
eventTypeId: eventType.id,
eventTypeSlug: eventType.slug,
usernameList: users,
startTime: selectedDate?.startOf("day"),
endTime: selectedDate?.endOf("day"),
timeZone,
duration,
/** Prevent refetching is we already have this data from month slots */
enabled: selectedDate && !monthSlots[selectedDate.format("YYYY-MM-DD")],
});
return (
<>
@ -178,7 +175,7 @@ const SlotPicker = ({
"mt-8 px-4 pb-4 sm:mt-0 md:min-w-[300px] md:px-5 lg:min-w-[455px]",
selectedDate ? "sm:dark:border-darkgray-200 border-gray-200 sm:border-r sm:p-4 sm:pr-6" : "sm:p-4"
)}
includedDates={Object.keys(slots).filter((k) => slots[k].length > 0)}
includedDates={Object.keys(monthSlots).filter((k) => monthSlots[k].length > 0)}
locale={isLocaleReady ? i18n.language : "en"}
selected={selectedDate}
onChange={(newDate) => {
@ -193,8 +190,12 @@ const SlotPicker = ({
<div ref={slotPickerRef}>
<AvailableTimes
isLoading={isLoading}
slots={selectedDate && slots[selectedDate.format("YYYY-MM-DD")]}
isLoading={isLoadingSelectedDateSlots}
slots={
selectedDate &&
(monthSlots[selectedDate.format("YYYY-MM-DD")] ||
selectedDateSlots[selectedDate.format("YYYY-MM-DD")])
}
date={selectedDate}
timeFormat={timeFormat}
onTimeFormatChange={onTimeFormatChange}

View File

@ -82,7 +82,6 @@ if (process.env.ANALYZE === "true") {
plugins.push(withTM);
plugins.push(withAxiom);
/** @type {import("next").NextConfig} */
const nextConfig = {
i18n,
@ -104,7 +103,7 @@ const nextConfig = {
images: {
unoptimized: true,
},
webpack: (config) => {
webpack: (config, { webpack, buildId }) => {
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
@ -126,6 +125,8 @@ const nextConfig = {
})
);
config.plugins.push(new webpack.DefinePlugin({ "process.env.BUILD_ID": JSON.stringify(buildId) }));
config.resolve.fallback = {
...config.resolve.fallback, // if you miss it, all the other options in fallback, specified
// by next.js will be dropped. Doesn't make much sense, but how it is

View File

@ -0,0 +1,52 @@
/**
* This page is empty for the user, it is used only to take advantage of the
* caching system that NextJS uses SSG pages.
* TODO: Redirect to user profile on browser
*/
import { GetStaticPaths, GetStaticProps } from "next";
import { z } from "zod";
import { getCachedResults } from "@calcom/core";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
const CalendarCache = () => <div />;
const paramsSchema = z.object({ user: z.string(), month: z.string() });
export const getStaticProps: GetStaticProps<
{ results: Awaited<ReturnType<typeof getCachedResults>> },
{ user: string }
> = async (context) => {
const { user: username, month } = paramsSchema.parse(context.params);
const user = await prisma.user.findUnique({
where: {
username,
},
select: {
id: true,
username: true,
credentials: true,
selectedCalendars: true,
},
});
const startDate = (
dayjs(month, "YYYY-MM").isSame(dayjs(), "month") ? dayjs() : dayjs(month, "YYYY-MM")
).startOf("day");
const endDate = startDate.endOf("month");
const results = user?.credentials
? await getCachedResults(user?.credentials, startDate.format(), endDate.format(), user?.selectedCalendars)
: [];
return {
props: { results, date: new Date().toISOString() },
revalidate: 1,
};
};
export const getStaticPaths: GetStaticPaths = () => {
return {
paths: [],
fallback: "blocking",
};
};
export default CalendarCache;

View File

@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
import notEmpty from "@calcom/lib/notEmpty";
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
import prisma from "@calcom/prisma";
import { getSession } from "@lib/auth";
@ -65,6 +66,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
res.status(200).json({ message: "Calendar Selection Saved" });
}
if (["DELETE", "POST"].includes(req.method)) {
await revalidateCalendarCache(res.revalidate, `${session?.user?.username}`);
}
if (req.method === "GET") {
const selectedCalendarIds = await prisma.selectedCalendar.findMany({
where: {

View File

@ -4,6 +4,7 @@ import type { Session } from "next-auth";
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
import { getSession } from "@calcom/lib/auth";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
import prisma from "@calcom/prisma";
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
@ -53,7 +54,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
/* Absolute path didn't work */
const handlerMap = (await import("@calcom/app-store/apps.server.generated")).apiHandlers;
const handlerKey = deriveAppDictKeyFromType(appName, handlerMap);
const handlers = await handlerMap[handlerKey as keyof typeof handlerMap];
const handler = handlers[apiEndpoint as keyof typeof handlers] as AppHandler;
@ -63,8 +63,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (typeof handler === "function") {
await handler(req, res);
if (appName.includes("calendar") && req.session?.user?.username) {
await revalidateCalendarCache(res.revalidate, req.session?.user?.username);
}
} else {
await defaultIntegrationAddHandler({ user: req.session?.user, ...handler });
if (handler.appType.includes("calendar") && req.session?.user?.username) {
await revalidateCalendarCache(res.revalidate, req.session?.user?.username);
}
redirectUrl = handler.redirect?.url || getInstalledAppPath(handler);
res.json({ url: redirectUrl, newTab: handler.redirect?.newTab });
}

View File

@ -0,0 +1,27 @@
import { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
const querySchema = z.object({
username: z.string(),
});
/**
* This endpoint revalidates users calendar cache several months ahead
* Can be used as webhook
* @param req
* @param res
* @returns
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { username } = querySchema.parse(req.query);
try {
await revalidateCalendarCache(res.revalidate, username);
return res.status(200).json({ revalidated: true });
} catch (err) {
return res.status(500).send({ message: "Error revalidating" });
}
}

View File

@ -1,11 +1,12 @@
import { SelectedCalendar } from "@prisma/client";
import { createHash } from "crypto";
import _ from "lodash";
import cache from "memory-cache";
import * as process from "process";
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
import getApps from "@calcom/app-store/utils";
import dayjs from "@calcom/dayjs";
import { getUid } from "@calcom/lib/CalEventParser";
import { WEBAPP_URL } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger";
import { performance } from "@calcom/lib/server/perfObserver";
import type { CalendarEvent, EventBusyDate, NewCalendarEventType } from "@calcom/types/Calendar";
@ -113,40 +114,27 @@ const cleanIntegrationKeys = (
return rest;
};
const CACHING_TIME = 30_000; // 30 seconds
const getCachedResults = async (
// here I will fetch the page json file.
export const getCachedResults = async (
withCredentials: CredentialPayload[],
dateFrom: string,
dateTo: string,
selectedCalendars: SelectedCalendar[]
) => {
): Promise<EventBusyDate[][]> => {
const calendarCredentials = withCredentials.filter((credential) => credential.type.endsWith("_calendar"));
const calendars = calendarCredentials.map((credential) => getCalendar(credential));
performance.mark("getBusyCalendarTimesStart");
const results = calendars.map(async (c, i) => {
/** Filter out nulls */
if (!c) return [];
/** We rely on the index so we can match credentials with calendars */
const { id, type, appId } = calendarCredentials[i];
const { type, appId } = calendarCredentials[i];
/** We just pass the calendars that matched the credential type,
* TODO: Migrate credential type or appId
*/
const passedSelectedCalendars = selectedCalendars.filter((sc) => sc.integration === type);
/** We extract external Ids so we don't cache too much */
const selectedCalendarIds = passedSelectedCalendars.map((sc) => sc.externalId);
/** We create a unique hash key based on the input data */
const cacheKey = JSON.stringify({ id, selectedCalendarIds, dateFrom, dateTo });
const cacheHashedKey = createHash("md5").update(cacheKey).digest("hex");
/** Check if we already have cached data and return */
const cachedAvailability = cache.get(cacheHashedKey);
if (cachedAvailability) {
log.debug(`Cache HIT: Calendar Availability for key: ${cacheKey}`);
return cachedAvailability;
}
log.debug(`Cache MISS: Calendar Availability for key ${cacheKey}`);
/** If we don't then we actually fetch external calendars (which can be very slow) */
performance.mark("eventBusyDatesStart");
const eventBusyDates = await c.getAvailability(dateFrom, dateTo, passedSelectedCalendars);
@ -156,12 +144,8 @@ const getCachedResults = async (
"eventBusyDatesStart",
"eventBusyDatesEnd"
);
const availability = eventBusyDates.map((a) => ({ ...a, source: `${appId}` }));
/** We save the availability to a few seconds so recurrent calls are nearly instant */
cache.put(cacheHashedKey, availability, CACHING_TIME);
return availability;
return eventBusyDates.map((a) => ({ ...a, source: `${appId}` }));
});
const awaitedResults = await Promise.all(results);
performance.mark("getBusyCalendarTimesEnd");
@ -173,17 +157,49 @@ const getCachedResults = async (
return awaitedResults;
};
/**
* This function fetch the json file that NextJS generates and uses to hydrate the static page on browser.
* If for some reason NextJS still doesn't generate this file, it will wait until it finishes generating it.
* On development environment it takes a long time because Next must compiles the whole page.
* @param username
* @param month A string representing year and month using YYYY-MM format
*/
const getNextCache = async (username: string, month: string): Promise<EventBusyDate[][]> => {
let localCache: EventBusyDate[][] = [];
try {
const { NODE_ENV } = process.env;
const cacheDir = `${NODE_ENV === "development" ? NODE_ENV : process.env.BUILD_ID}`;
const baseUrl = `${WEBAPP_URL}/_next/data/${cacheDir}/en`;
console.log(`${baseUrl}/${username}/calendar-cache/${month}.json?user=${username}&month=${month}`);
localCache = await fetch(
`${baseUrl}/${username}/calendar-cache/${month}.json?user=${username}&month=${month}`
)
.then((r) => r.json())
.then((json) => json?.pageProps?.results);
} catch (e) {
log.warn(e);
}
return localCache;
};
export const getBusyCalendarTimes = async (
username: string,
withCredentials: CredentialPayload[],
dateFrom: string,
dateTo: string,
selectedCalendars: SelectedCalendar[]
dateTo: string
) => {
let results: EventBusyDate[][] = [];
try {
results = await getCachedResults(withCredentials, dateFrom, dateTo, selectedCalendars);
} catch (error) {
log.warn(error);
if (dayjs(dateFrom).isSame(dayjs(dateTo), "month")) {
results = await getNextCache(username, dayjs(dateFrom).format("YYYY-MM"));
} else {
// if dateFrom and dateTo is from different months get cache by each month
const monthsOfDiff = dayjs(dateTo).diff(dayjs(dateFrom), "month");
const months: string[] = [dayjs(dateFrom).format("YYYY-MM")];
for (let i = 1; i <= monthsOfDiff; i++) {
months.push(dayjs(dateFrom).add(i, "month").format("YYYY-MM"));
}
const data: EventBusyDate[][][] = await Promise.all(months.map((month) => getNextCache(username, month)));
results = data.flat(1);
}
return results.reduce((acc, availability) => acc.concat(availability), []);
};

View File

@ -10,20 +10,20 @@ import type { EventBusyDetails } from "@calcom/types/Calendar";
export async function getBusyTimes(params: {
credentials: Credential[];
userId: number;
username: string;
eventTypeId?: number;
startTime: string;
beforeEventBuffer?: number;
afterEventBuffer?: number;
endTime: string;
selectedCalendars: SelectedCalendar[];
}) {
const {
credentials,
userId,
username,
eventTypeId,
startTime,
endTime,
selectedCalendars,
beforeEventBuffer,
afterEventBuffer,
} = params;
@ -75,7 +75,7 @@ export async function getBusyTimes(params: {
performance.mark("prismaBookingGetEnd");
performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd");
if (credentials?.length > 0) {
const calendarBusyTimes = await getBusyCalendarTimes(credentials, startTime, endTime, selectedCalendars);
const calendarBusyTimes = await getBusyCalendarTimes(username, credentials, startTime, endTime);
busyTimes.push(
...calendarBusyTimes.map((value) => ({
...value,

View File

@ -121,8 +121,7 @@ export async function getUserAvailability(
if (username) where.username = username;
if (userId) where.id = userId;
let user: User | null = initialData?.user || null;
if (!user) user = await getUser(where);
const user = initialData?.user || (await getUser(where));
if (!user) throw new HttpError({ statusCode: 404, message: "No user found" });
let eventType: EventType | null = initialData?.eventType || null;
@ -137,15 +136,13 @@ export async function getUserAvailability(
const bookingLimits = parseBookingLimit(eventType?.bookingLimits);
const { selectedCalendars, ...currentUser } = user;
const busyTimes = await getBusyTimes({
credentials: currentUser.credentials,
credentials: user.credentials,
startTime: dateFrom.toISOString(),
endTime: dateTo.toISOString(),
eventTypeId,
userId: currentUser.id,
selectedCalendars,
userId: user.id,
username: `${user.username}`,
beforeEventBuffer,
afterEventBuffer,
});
@ -222,8 +219,8 @@ export async function getUserAvailability(
}
}
const userSchedule = currentUser.schedules.filter(
(schedule) => !currentUser.defaultScheduleId || schedule.id === currentUser.defaultScheduleId
const userSchedule = user.schedules.filter(
(schedule) => !user?.defaultScheduleId || schedule.id === user?.defaultScheduleId
)[0];
const schedule =
@ -233,17 +230,16 @@ export async function getUserAvailability(
...userSchedule,
availability: userSchedule.availability.map((a) => ({
...a,
userId: currentUser.id,
userId: user.id,
})),
};
const startGetWorkingHours = performance.now();
const timeZone = schedule.timeZone || eventType?.timeZone || currentUser.timeZone;
const timeZone = schedule.timeZone || eventType?.timeZone || user.timeZone;
const availability =
schedule.availability ||
(eventType?.availability.length ? eventType.availability : currentUser.availability);
schedule.availability || (eventType?.availability.length ? eventType.availability : user.availability);
const workingHours = getWorkingHours({ timeZone }, availability);

View File

@ -0,0 +1,18 @@
import { NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
export const revalidateCalendarCache = (
revalidate: NextApiResponse["revalidate"],
username: string,
monthsToRevalidate = 4
): Promise<void[]> => {
return Promise.all(
new Array(monthsToRevalidate).fill(0).map((_, index): Promise<void> => {
const date = dayjs().add(index, "month").format("YYYY-MM");
const url = `/${username}/calendar-cache/${date}`;
console.log("revalidating", url);
return revalidate(url);
})
);
};

View File

@ -1131,6 +1131,11 @@ const loggedInViewerRouter = router({
id: id,
},
});
// Revalidate user calendar cache.
if (credential.app?.slug.includes("calendar")) {
const baseURL = process.env.VERCEL_URL || process.env.NEXT_PUBLIC_WEBAPP_URL;
await fetch(`${baseURL}/api/revalidate-calendar-cache/${ctx?.user?.username}`);
}
}),
bookingUnconfirmedCount: authedProcedure.query(async ({ ctx }) => {
const { prisma, user } = ctx;

View File

@ -239,6 +239,7 @@
"$NEXTAUTH_SECRET",
"$NEXTAUTH_URL",
"$NODE_ENV",
"$BUILD_ID",
"$PLAYWRIGHT_HEADLESS",
"$PLAYWRIGHT_TEST_BASE_URL",
"$PRISMA_FIELD_ENCRYPTION_KEY",