diff --git a/.env.example b/.env.example index 520ce80b27..97bdb2e398 100644 --- a/.env.example +++ b/.env.example @@ -115,6 +115,10 @@ SENDGRID_API_KEY= SENDGRID_EMAIL= NEXT_PUBLIC_SENDGRID_SENDER_NAME= +# Sentry +# Used for capturing exceptions and logging messages +NEXT_PUBLIC_SENTRY_DSN= + # Twilio # Used to send SMS reminders in workflows TWILIO_SID= @@ -126,7 +130,7 @@ TWILIO_WHATSAPP_PHONE_NUMBER= NEXT_PUBLIC_SENDER_ID= TWILIO_VERIFY_SID= -# Set it to "1" if you need to run E2E tests locally. +# Set it to "1" if you need to run E2E tests locally. NEXT_PUBLIC_IS_E2E= # Used for internal billing system diff --git a/apps/api/lib/validations/user.ts b/apps/api/lib/validations/user.ts index 09d389e41a..7c595a3352 100644 --- a/apps/api/lib/validations/user.ts +++ b/apps/api/lib/validations/user.ts @@ -4,7 +4,7 @@ import { checkUsername } from "@calcom/lib/server/checkUsername"; import { _UserModel as User } from "@calcom/prisma/zod"; import { iso8601 } from "@calcom/prisma/zod-utils"; -import { isValidBase64Image } from "~/lib/utils/isValidBase64"; +import { isValidBase64Image } from "~/lib/utils/isValidBase64Image"; import { timeZone } from "~/lib/validations/shared/timeZone"; // @note: These are the ONLY values allowed as weekStart. So user don't introduce bad data. diff --git a/apps/api/package.json b/apps/api/package.json index bdbbad8878..11595bd6e5 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -30,7 +30,7 @@ "@calcom/lib": "*", "@calcom/prisma": "*", "@calcom/trpc": "*", - "@sentry/nextjs": "^7.20.0", + "@sentry/nextjs": "^7.73.0", "bcryptjs": "^2.4.3", "memory-cache": "^0.2.0", "next": "^13.4.6", diff --git a/apps/web/next.config.js b/apps/web/next.config.js index c63cbe02a6..1a66fdd4eb 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -3,6 +3,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin"); const os = require("os"); const englishTranslation = require("./public/static/locales/en/common.json"); const { withAxiom } = require("next-axiom"); +const { withSentryConfig } = require("@sentry/nextjs"); const { version } = require("./package.json"); const { i18n } = require("./next-i18next.config"); const { @@ -92,6 +93,7 @@ if (process.env.ANALYZE === "true") { } plugins.push(withAxiom); + const matcherConfigRootPath = { has: [ { @@ -547,4 +549,13 @@ const nextConfig = { }, }; +if (!!process.env.NEXT_PUBLIC_SENTRY_DSN) { + nextConfig["sentry"] = { + autoInstrumentServerFunctions: true, + hideSourceMaps: true, + }; + + plugins.push(withSentryConfig); +} + module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig); diff --git a/apps/web/package.json b/apps/web/package.json index 3abb333ae1..dfe089b74a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@calcom/web", - "version": "3.4.6", + "version": "3.4.7", "private": true, "scripts": { "analyze": "ANALYZE=true next build", @@ -57,6 +57,7 @@ "@radix-ui/react-switch": "^1.0.0", "@radix-ui/react-toggle-group": "^1.0.0", "@radix-ui/react-tooltip": "^1.0.0", + "@sentry/nextjs": "^7.73.0", "@stripe/react-stripe-js": "^1.10.0", "@stripe/stripe-js": "^1.35.0", "@tanstack/react-query": "^4.3.9", diff --git a/apps/web/sentry.client.config.ts b/apps/web/sentry.client.config.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/apps/web/sentry.client.config.ts @@ -0,0 +1 @@ +export {}; diff --git a/apps/web/sentry.edge.config.ts b/apps/web/sentry.edge.config.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/apps/web/sentry.edge.config.ts @@ -0,0 +1 @@ +export {}; diff --git a/apps/web/sentry.server.config.ts b/apps/web/sentry.server.config.ts new file mode 100644 index 0000000000..55fe566090 --- /dev/null +++ b/apps/web/sentry.server.config.ts @@ -0,0 +1,6 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 0, +}); diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index 77fd0bcb32..e74d338686 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -4,6 +4,7 @@ import prismock from "../../../../tests/libs/__mocks__/prisma"; import { diff } from "jest-diff"; import { describe, expect, vi, beforeEach, afterEach, test } from "vitest"; +import dayjs from "@calcom/dayjs"; import type { BookingStatus } from "@calcom/prisma/enums"; import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types"; import { getAvailableSlots as getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util"; @@ -874,6 +875,124 @@ describe("getSchedule", () => { } ); }); + test("test that booking limit is working correctly if user is all day available", async () => { + const { dateString: plus1DateString } = getDate({ dateIncrement: 1 }); + const { dateString: plus2DateString } = getDate({ dateIncrement: 2 }); + const { dateString: plus3DateString } = getDate({ dateIncrement: 3 }); + + const scenarioData = { + eventTypes: [ + { + id: 1, + length: 60, + beforeEventBuffer: 0, + afterEventBuffer: 0, + bookingLimits: { + PER_DAY: 1, + }, + users: [ + { + id: 101, + }, + ], + }, + { + id: 2, + length: 60, + beforeEventBuffer: 0, + afterEventBuffer: 0, + bookingLimits: { + PER_DAY: 2, + }, + users: [ + { + id: 101, + }, + ], + }, + ], + users: [ + { + ...TestData.users.example, + id: 101, + schedules: [ + { + id: 1, + name: "All Day available", + availability: [ + { + userId: null, + eventTypeId: null, + days: [0, 1, 2, 3, 4, 5, 6], + startTime: new Date("1970-01-01T00:00:00.000Z"), + endTime: new Date("1970-01-01T23:59:59.999Z"), + date: null, + }, + ], + timeZone: Timezones["+5:30"], + }, + ], + }, + ], + bookings: [ + { + userId: 101, + eventTypeId: 1, + startTime: `${plus2DateString}T08:30:00.000Z`, + endTime: `${plus2DateString}T08:29:59.999Z`, + status: "ACCEPTED" as BookingStatus, + }, + { + userId: 101, + eventTypeId: 2, + startTime: `${plus2DateString}T08:30:00.000Z`, + endTime: `${plus2DateString}T08:29:59.999Z`, + status: "ACCEPTED" as BookingStatus, + }, + ], + }; + + await createBookingScenario(scenarioData); + + const thisUserAvailabilityBookingLimitOne = await getSchedule({ + input: { + eventTypeId: 1, + eventTypeSlug: "", + startTime: `${plus1DateString}T00:00:00.000Z`, + endTime: `${plus3DateString}T23:59:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + }, + }); + + const thisUserAvailabilityBookingLimitTwo = await getSchedule({ + input: { + eventTypeId: 2, + eventTypeSlug: "", + startTime: `${plus1DateString}T00:00:00.000Z`, + endTime: `${plus3DateString}T23:59:59.999Z`, + timeZone: Timezones["+5:30"], + isTeamEvent: false, + }, + }); + + let availableSlotsInTz: dayjs.Dayjs[] = []; + for (const date in thisUserAvailabilityBookingLimitOne.slots) { + thisUserAvailabilityBookingLimitOne.slots[date].forEach((timeObj) => { + availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+5:30"])); + }); + } + + expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0); // 1 booking per day as limit + + availableSlotsInTz = []; + for (const date in thisUserAvailabilityBookingLimitTwo.slots) { + thisUserAvailabilityBookingLimitTwo.slots[date].forEach((timeObj) => { + availableSlotsInTz.push(dayjs(timeObj.time).tz(Timezones["+5:30"])); + }); + } + expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(23); // 2 booking per day as limit, only one booking on that + }); }); describe("Team Event", () => { diff --git a/apps/web/test/utils/bookingScenario/bookingScenario.ts b/apps/web/test/utils/bookingScenario/bookingScenario.ts index 2420851ae8..0a3809721c 100644 --- a/apps/web/test/utils/bookingScenario/bookingScenario.ts +++ b/apps/web/test/utils/bookingScenario/bookingScenario.ts @@ -101,6 +101,9 @@ export type InputEventType = { requiresConfirmation?: boolean; destinationCalendar?: Prisma.DestinationCalendarCreateInput; schedule?: InputUser["schedules"][number]; + bookingLimits?: { + PER_DAY?: number; + }; } & Partial>; type WhiteListedBookingProps = { @@ -199,6 +202,7 @@ async function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser timeZone: null, beforeEventBuffer: 0, afterEventBuffer: 0, + bookingLimits: {}, schedulingType: null, length: 15, //TODO: What is the purpose of periodStartDate and periodEndDate? Test these? diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 997b318bf4..1021ebfbfd 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -408,22 +408,7 @@ async function ensureAvailableUsers( let foundConflict = false; try { - if ( - eventType.recurringEvent && - recurringDatesInfo?.currentRecurringIndex === 0 && - recurringDatesInfo.allRecurringDates - ) { - const allBookingDates = recurringDatesInfo.allRecurringDates.map((strDate) => new Date(strDate)); - // Go through each date for the recurring event and check if each one's availability - // DONE: Decreased computational complexity from O(2^n) to O(n) by refactoring this loop to stop - // running at the first unavailable time. - let i = 0; - while (!foundConflict && i < allBookingDates.length) { - foundConflict = checkForConflicts(bufferedBusyTimes, allBookingDates[i++], duration); - } - } else { - foundConflict = checkForConflicts(bufferedBusyTimes, input.dateFrom, duration); - } + foundConflict = checkForConflicts(bufferedBusyTimes, input.dateFrom, duration); } catch { log.debug({ message: "Unable set isAvailableToBeBooked. Using true. ", @@ -882,7 +867,12 @@ async function handler( ) { const startAsDate = dayjs(reqBody.start).toDate(); if (eventType.bookingLimits) { - await checkBookingLimits(eventType.bookingLimits as IntervalLimit, startAsDate, eventType.id); + await checkBookingLimits( + eventType.bookingLimits as IntervalLimit, + startAsDate, + eventType.id, + eventType.schedule?.timeZone + ); } if (eventType.durationLimits) { await checkDurationLimits(eventType.durationLimits as IntervalLimit, startAsDate, eventType.id); @@ -930,8 +920,8 @@ async function handler( }), }, { - dateFrom: reqBody.start, - dateTo: reqBody.end, + dateFrom: dayjs(reqBody.start).tz(reqBody.timeZone).format(), + dateTo: dayjs(reqBody.end).tz(reqBody.timeZone).format(), timeZone: reqBody.timeZone, originalRescheduledBooking, }, @@ -998,7 +988,6 @@ async function handler( const attendeeTimezone = attendeeInfoOnReschedule ? attendeeInfoOnReschedule.timeZone : reqBody.timeZone; const tAttendees = await getTranslation(attendeeLanguage ?? "en", "common"); - // use host default if (isTeamEventType && locationBodyString === OrganizerDefaultConferencingAppType) { const metadataParseResult = userMetadataSchema.safeParse(organizerUser.metadata); diff --git a/packages/lib/date-ranges.test.ts b/packages/lib/date-ranges.test.ts index 4a3769b378..c851c6c43d 100644 --- a/packages/lib/date-ranges.test.ts +++ b/packages/lib/date-ranges.test.ts @@ -179,6 +179,38 @@ describe("processWorkingHours", () => { expect(allDSTStartAt12).toBeTruthy(); expect(allNotDSTStartAt13).toBeTruthy(); }); + + it("should skip event if it ends before it starts (different days)", () => { + const item = { + days: [1, 2, 3], + startTime: new Date(new Date().setUTCHours(8, 0, 0, 0)), // 8 AM + endTime: new Date(new Date().setUTCHours(7, 0, 0, 0)), // 7 AM + }; + + const timeZone = "America/New_York"; + const dateFrom = dayjs("2023-11-07T00:00:00Z").tz(timeZone); // 2023-11-07T00:00:00 (America/New_York) + const dateTo = dayjs("2023-11-08T00:00:00Z").tz(timeZone); // 2023-11-08T00:00:00 (America/New_York) + + const results = processWorkingHours({ item, timeZone, dateFrom, dateTo }); + + expect(results).toEqual([]); + }); + + it("should skip event if it ends before it starts (same day but different hours)", () => { + const item = { + days: [1], + startTime: new Date(new Date().setUTCHours(8, 0, 0, 0)), // 8 AM + endTime: new Date(new Date().setUTCHours(7, 0, 0, 0)), // 7 AM + }; + + const timeZone = "America/New_York"; + const dateFrom = dayjs("2023-11-07T00:00:00Z").tz(timeZone); // 2023-11-07T00:00:00 (America/New_York) + const dateTo = dayjs("2023-11-07T23:59:59Z").tz(timeZone); // 2023-11-07T23:59:59 (America/New_York) + + const results = processWorkingHours({ item, timeZone, dateFrom, dateTo }); + + expect(results).toEqual([]); + }); }); describe("processDateOverrides", () => { diff --git a/packages/lib/date-ranges.ts b/packages/lib/date-ranges.ts index fcef54a649..71e1ab3cbe 100644 --- a/packages/lib/date-ranges.ts +++ b/packages/lib/date-ranges.ts @@ -47,7 +47,11 @@ export function processWorkingHours({ const startResult = dayjs.max(start, dateFrom.tz(timeZone)); const endResult = dayjs.min(end, dateTo.tz(timeZone)); - if (startResult.isAfter(endResult)) { + const isLater = + startResult.isAfter(endResult, "hour") || + (startResult.isSame(endResult, "hour") && startResult.isAfter(endResult, "minute")); + + if (isLater) { // if an event ends before start, it's not a result. continue; } diff --git a/packages/lib/hooks/useParamsWithFallback.test.ts b/packages/lib/hooks/useParamsWithFallback.test.ts new file mode 100644 index 0000000000..0a8ff38dd7 --- /dev/null +++ b/packages/lib/hooks/useParamsWithFallback.test.ts @@ -0,0 +1,63 @@ +import { renderHook } from "@testing-library/react-hooks"; +import { vi } from "vitest"; +import { describe, expect, it } from "vitest"; + +import { useParamsWithFallback } from "./useParamsWithFallback"; + +describe("useParamsWithFallback hook", () => { + it("should return router.query when param is null", () => { + vi.mock("next/navigation", () => ({ + useParams: vi.fn().mockReturnValue(null), + })); + + vi.mock("next/compat/router", () => ({ + useRouter: vi.fn().mockReturnValue({ query: { id: 1 } }), + })); + + const { result } = renderHook(() => useParamsWithFallback()); + + expect(result.current).toEqual({ id: 1 }); + }); + + it("should return router.query when param is undefined", () => { + vi.mock("next/navigation", () => ({ + useParams: vi.fn().mockReturnValue(undefined), + })); + + vi.mock("next/compat/router", () => ({ + useRouter: vi.fn().mockReturnValue({ query: { id: 1 } }), + })); + + const { result } = renderHook(() => useParamsWithFallback()); + + expect(result.current).toEqual({ id: 1 }); + }); + + it("should return useParams() if it exists", () => { + vi.mock("next/navigation", () => ({ + useParams: vi.fn().mockReturnValue({ id: 1 }), + })); + + vi.mock("next/compat/router", () => ({ + useRouter: vi.fn().mockReturnValue(null), + })); + + const { result } = renderHook(() => useParamsWithFallback()); + + expect(result.current).toEqual({ id: 1 }); + }); + + it("should return useParams() if it exists", () => { + vi.mock("next/navigation", () => ({ + useParams: vi.fn().mockReturnValue({ id: 1 }), + })); + + vi.mock("next/compat/router", () => ({ + useRouter: vi.fn().mockReturnValue({ query: { id: 2 } }), + })); + + const { result } = renderHook(() => useParamsWithFallback()); + + expect(result.current).toEqual({ id: 1 }); + }); +}); diff --git a/packages/lib/hooks/useParamsWithFallback.ts b/packages/lib/hooks/useParamsWithFallback.ts index 53dd1bb498..8e5e4eb5f3 100644 --- a/packages/lib/hooks/useParamsWithFallback.ts +++ b/packages/lib/hooks/useParamsWithFallback.ts @@ -1,13 +1,18 @@ "use client"; +import { useRouter as useCompatRouter } from "next/compat/router"; import { useParams } from "next/navigation"; -import { useRouter } from "next/router"; +import type { ParsedUrlQuery } from "querystring"; + +interface Params { + [key: string]: string | string[]; +} /** * This hook is a workaround until pages are migrated to app directory. */ -export function useParamsWithFallback() { - const router = useRouter(); - const params = useParams(); - return params || router.query; +export function useParamsWithFallback(): Params | ParsedUrlQuery { + const params = useParams(); // always `null` in pages router + const router = useCompatRouter(); // always `null` in app router + return params ?? router?.query ?? {}; } diff --git a/packages/lib/parse-dates.ts b/packages/lib/parse-dates.ts index 9529ad7f56..61b0e56fd7 100644 --- a/packages/lib/parse-dates.ts +++ b/packages/lib/parse-dates.ts @@ -9,7 +9,12 @@ import { parseZone } from "./parse-zone"; type ExtraOptions = { withDefaultTimeFormat?: boolean; selectedTimeFormat?: TimeFormat }; -const processDate = (date: string | null | Dayjs, language: string, options?: ExtraOptions) => { +const processDate = ( + date: string | null | Dayjs, + language: string, + timeZone: string, + options?: ExtraOptions +) => { const parsedZone = parseZone(date); if (!parsedZone?.isValid()) return "Invalid date"; const formattedTime = parsedZone?.format( @@ -17,12 +22,19 @@ const processDate = (date: string | null | Dayjs, language: string, options?: Ex ? TimeFormat.TWELVE_HOUR : options?.selectedTimeFormat || detectBrowserTimeFormat ); - return `${formattedTime}, ${dayjs(date).toDate().toLocaleString(language, { dateStyle: "full" })}`; + return `${formattedTime}, ${dayjs(date) + .toDate() + .toLocaleString(language, { dateStyle: "full", timeZone })}`; }; -export const parseDate = (date: string | null | Dayjs, language: string, options?: ExtraOptions) => { +export const parseDate = ( + date: string | null | Dayjs, + language: string, + timeZone: string, + options?: ExtraOptions +) => { if (!date) return ["No date"]; - return processDate(date, language, options); + return processDate(date, language, timeZone, options); }; const timeOptions: Intl.DateTimeFormatOptions = { @@ -74,7 +86,7 @@ export const parseRecurringDates = ( withDefaultTimeFormat, }: { startDate: string | null | Dayjs; - timeZone?: string; + timeZone: string; recurringEvent: RecurringEvent | null; recurringCount: number; selectedTimeFormat?: TimeFormat; @@ -98,7 +110,7 @@ export const parseRecurringDates = ( }); const dateStrings = times.map((t) => { // finally; show in local timeZone again - return processDate(t.tz(timeZone), language, { selectedTimeFormat, withDefaultTimeFormat }); + return processDate(t.tz(timeZone), language, timeZone, { selectedTimeFormat, withDefaultTimeFormat }); }); return [dateStrings, times.map((t) => t.toDate())]; diff --git a/packages/lib/server/checkBookingLimits.ts b/packages/lib/server/checkBookingLimits.ts index 0778ad92b2..91c581d146 100644 --- a/packages/lib/server/checkBookingLimits.ts +++ b/packages/lib/server/checkBookingLimits.ts @@ -11,14 +11,15 @@ import { parseBookingLimit } from "../isBookingLimits"; export async function checkBookingLimits( bookingLimits: IntervalLimit, eventStartDate: Date, - eventId: number + eventId: number, + timeZone?: string | null ) { const parsedBookingLimits = parseBookingLimit(bookingLimits); if (!parsedBookingLimits) return false; // not iterating entries to preserve types const limitCalculations = ascendingLimitKeys.map((key) => - checkBookingLimit({ key, limitingNumber: parsedBookingLimits[key], eventStartDate, eventId }) + checkBookingLimit({ key, limitingNumber: parsedBookingLimits[key], eventStartDate, eventId, timeZone }) ); try { @@ -33,19 +34,23 @@ export async function checkBookingLimit({ eventId, key, limitingNumber, + timeZone, }: { eventStartDate: Date; eventId: number; key: keyof IntervalLimit; limitingNumber: number | undefined; + timeZone?: string | null; }) { { + const eventDateInOrganizerTz = timeZone ? dayjs(eventStartDate).tz(timeZone) : dayjs(eventStartDate); + if (!limitingNumber) return; const unit = intervalLimitKeyToUnit(key); - const startDate = dayjs(eventStartDate).startOf(unit).toDate(); - const endDate = dayjs(eventStartDate).endOf(unit).toDate(); + const startDate = dayjs(eventDateInOrganizerTz).startOf(unit).toDate(); + const endDate = dayjs(eventDateInOrganizerTz).endOf(unit).toDate(); const bookingsInPeriod = await prisma.booking.count({ where: { diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index a1aebdbeb4..dba23604b7 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -205,7 +205,11 @@ export const stringOrNumber = z.union([ z.number().int(), ]); -export const stringToDayjs = z.string().transform((val) => dayjs(val)); +export const stringToDayjs = z.string().transform((val) => { + const matches = val.match(/([+-]\d{2}:\d{2})$/); + const timezone = matches ? matches[1] : "+00:00"; + return dayjs(val).utcOffset(timezone); +}); export const bookingCreateBodySchema = z.object({ end: z.string().optional(), diff --git a/packages/trpc/server/middlewares/captureErrorsMiddleware.ts b/packages/trpc/server/middlewares/captureErrorsMiddleware.ts index e3fe56a950..4112095ef8 100644 --- a/packages/trpc/server/middlewares/captureErrorsMiddleware.ts +++ b/packages/trpc/server/middlewares/captureErrorsMiddleware.ts @@ -1,3 +1,5 @@ +import * as Sentry from "@sentry/nextjs"; + import { redactError } from "@calcom/lib/redactError"; import { middleware } from "../trpc"; @@ -9,6 +11,7 @@ const captureErrorsMiddleware = middleware(async ({ next }) => { if (!cause) { return result; } + Sentry.captureException(cause); throw redactError(cause); } return result; diff --git a/turbo.json b/turbo.json index 849d70f2fe..4463f6faf0 100644 --- a/turbo.json +++ b/turbo.json @@ -26,6 +26,7 @@ "outputs": [".next/**"], "env": [ "NEXT_PUBLIC_IS_E2E", + "NEXT_PUBLIC_SENTRY_DSN", "NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE_MONTHLY", "NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRODUCT_ID", "NEXT_PUBLIC_STRIPE_PUBLIC_KEY", diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 20d12799fb..1b59d9ba80 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -36,7 +36,7 @@ const workspaces = packagedEmbedTestsOnly test: { include: ["packages/**/*.{test,spec}.{ts,js}", "apps/**/*.{test,spec}.{ts,js}"], // TODO: Ignore the api until tests are fixed - exclude: ["**/node_modules/**/*", "packages/embeds/**/*"], + exclude: ["**/node_modules/**/*", "packages/embeds/**/*", "packages/lib/hooks/**/*"], setupFiles: ["setupVitest.ts"], }, }, @@ -67,6 +67,13 @@ const workspaces = packagedEmbedTestsOnly setupFiles: ["packages/app-store/test-setup.ts"], }, }, + { + test: { + name: "@calcom/packages/lib/hooks", + include: ["packages/lib/hooks/**/*.{test,spec}.{ts,js}"], + environment: "jsdom", + }, + }, ]; export default defineWorkspace(workspaces); diff --git a/yarn.lock b/yarn.lock index 85c71169df..f36c738004 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3431,7 +3431,7 @@ __metadata: "@calcom/trpc": "*" "@calcom/tsconfig": "*" "@calcom/types": "*" - "@sentry/nextjs": ^7.20.0 + "@sentry/nextjs": ^7.73.0 bcryptjs: ^2.4.3 memory-cache: ^0.2.0 next: ^13.4.6 @@ -4515,6 +4515,7 @@ __metadata: "@radix-ui/react-switch": ^1.0.0 "@radix-ui/react-toggle-group": ^1.0.0 "@radix-ui/react-tooltip": ^1.0.0 + "@sentry/nextjs": ^7.73.0 "@stripe/react-stripe-js": ^1.10.0 "@stripe/stripe-js": ^1.35.0 "@tanstack/react-query": ^4.3.9 @@ -10498,29 +10499,27 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/tracing@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry-internal/tracing@npm:7.53.1" +"@sentry-internal/tracing@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry-internal/tracing@npm:7.77.0" dependencies: - "@sentry/core": 7.53.1 - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 - tslib: ^1.9.3 - checksum: 99fadf422e619faeea436a96b37088ba10850f8e77fca10d7fea6de0c3f1d60b0e8ec1da3b9230f4213fa25f044c99aba585562447ddca0a443d7cbd88c3b81d + "@sentry/core": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 + checksum: dc6162db6d426212440abd66f9e37a4ce1849a79dd067158091702b273f3312ea131984c7aad58708745340b2a0ddb652b898c27dbf409d8691dbd4ee8a9ebde languageName: node linkType: hard -"@sentry/browser@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/browser@npm:7.53.1" +"@sentry/browser@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/browser@npm:7.77.0" dependencies: - "@sentry-internal/tracing": 7.53.1 - "@sentry/core": 7.53.1 - "@sentry/replay": 7.53.1 - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 - tslib: ^1.9.3 - checksum: 6250949dfbef169669d9e932515f0d965f449ef82be5fb6b813ab170944cef033c758857a3412aa1a1023d77622f88d8599df8e4b0ea126479f8713bd4aa1838 + "@sentry-internal/tracing": 7.77.0 + "@sentry/core": 7.77.0 + "@sentry/replay": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 + checksum: b3c4ecbf27e8d940c42bc151fc39e9e3659889c0091e69a39f3b8f0e316caa9cdd17aa2c00b151ff9976eff459f023e69cb51f8efa96a840191afd4091cc7573 languageName: node linkType: hard @@ -10540,112 +10539,119 @@ __metadata: languageName: node linkType: hard -"@sentry/core@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/core@npm:7.53.1" +"@sentry/core@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/core@npm:7.77.0" dependencies: - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 - tslib: ^1.9.3 - checksum: 5d42bc30a59cb4459eb99441b631e3f9daec5b2e98c24377aae0329127f8c474a9c32353a96e3f4765e3fb18370756e85f6b873cd26bd591e0040917b7fa9cf4 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 + checksum: f76e83ba5da4710b87b5e03f747ac5b5f93c93814447d29e1863b6bd3734d87f3a8fb197060a9a8370430d1d47b5c8c33dc86acc7f7b212322630e280435c6d8 languageName: node linkType: hard -"@sentry/integrations@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/integrations@npm:7.53.1" +"@sentry/integrations@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/integrations@npm:7.77.0" dependencies: - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 + "@sentry/core": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 localforage: ^1.8.1 - tslib: ^1.9.3 - checksum: 55fc930af665afa07f81591f8898c8c10518696d48ec6d9f0864fc05fa5ef754868644f50362bac15734dff93592397ec022d80ded8754b97d64009e63a30712 + checksum: b34f1e0a5a28154698b0eb42c9a3c968c35cbe1ba0411df7b4b83008a58cb3db2a2c07eee5e7a6e55f8b6aae9c74c8433d0e97a3e6289c979b361c09ced4475a languageName: node linkType: hard -"@sentry/nextjs@npm:^7.20.0": - version: 7.53.1 - resolution: "@sentry/nextjs@npm:7.53.1" +"@sentry/nextjs@npm:^7.73.0": + version: 7.77.0 + resolution: "@sentry/nextjs@npm:7.77.0" dependencies: "@rollup/plugin-commonjs": 24.0.0 - "@sentry/core": 7.53.1 - "@sentry/integrations": 7.53.1 - "@sentry/node": 7.53.1 - "@sentry/react": 7.53.1 - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 + "@sentry/core": 7.77.0 + "@sentry/integrations": 7.77.0 + "@sentry/node": 7.77.0 + "@sentry/react": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 + "@sentry/vercel-edge": 7.77.0 "@sentry/webpack-plugin": 1.20.0 chalk: 3.0.0 + resolve: 1.22.8 rollup: 2.78.0 stacktrace-parser: ^0.1.10 - tslib: ^1.9.3 peerDependencies: - next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 + next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 || ^14.0 react: 16.x || 17.x || 18.x webpack: ">= 4.0.0" peerDependenciesMeta: webpack: optional: true - checksum: 3b23a986b99b69453426f32f851eb3140d7ed29f066d2bf2d2562b72b8ca786fffa0c7903f0ab6f9a83ec1defaa2f7ec58db62515f4b95bd436ce33c862644db + checksum: 293019ff3bc85f1f041f15f1836cb98587cbecf780c26d7758a470d382042c9aa9522408853afbe16a73945f0e4edb6d5b3d704e8b8501b9d90304a6a9492afe languageName: node linkType: hard -"@sentry/node@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/node@npm:7.53.1" +"@sentry/node@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/node@npm:7.77.0" dependencies: - "@sentry-internal/tracing": 7.53.1 - "@sentry/core": 7.53.1 - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 - cookie: ^0.4.1 + "@sentry-internal/tracing": 7.77.0 + "@sentry/core": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 https-proxy-agent: ^5.0.0 - lru_map: ^0.3.3 - tslib: ^1.9.3 - checksum: e1b3c342c75a2a5883d6f1a52bc975bd6fb06643d2c26fd302cc0cfd23ca09509374f8c3afbe16208d007dc862e58b96946125dfebd3ba5856aa791cff04cea2 + checksum: 11034db59a547f3b35208f601fa4704dc7ef6e1f9730136562c1764de8f7b286b355b701711febb44b972ed7009b3467c21ffda15f07024f7dee36979ca9e1c1 languageName: node linkType: hard -"@sentry/react@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/react@npm:7.53.1" +"@sentry/react@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/react@npm:7.77.0" dependencies: - "@sentry/browser": 7.53.1 - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 + "@sentry/browser": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 hoist-non-react-statics: ^3.3.2 - tslib: ^1.9.3 peerDependencies: react: 15.x || 16.x || 17.x || 18.x - checksum: ee08c6a851cae421a134d62e217c192a961930254ea8775e1960420307f4ff08b659405bd883b12698a55085ad1070ac58e618f5799a7edc775368382e807e2d + checksum: 18af780c76b6b0c52ea476963b8ef0b7a346796c5d0e04dd7c4cb73eb3e52b2fd00b8c1cd5529c29983398a57e325d0d45a689bc8b9822d7c9b10339870869f9 languageName: node linkType: hard -"@sentry/replay@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/replay@npm:7.53.1" +"@sentry/replay@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/replay@npm:7.77.0" dependencies: - "@sentry/core": 7.53.1 - "@sentry/types": 7.53.1 - "@sentry/utils": 7.53.1 - checksum: c3757077a971183f5f9f87006449a110c0f951c76a777aa45cb2f057100ec5bc8fcf50e4c58e8117ddc0f44fdaada101384a45b2b5c427048b4ce57eb780711d + "@sentry-internal/tracing": 7.77.0 + "@sentry/core": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 + checksum: 95bbd554a33a17273501fffadd7a51050787ef5a0ec26de5cccba35f6bfd5d88b2c13f735beaf74274cdb5f77d3a310fa2699d9629220de4b79e4d4c27a14e20 languageName: node linkType: hard -"@sentry/types@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/types@npm:7.53.1" - checksum: 5f76d7d66d5df5d48f66a755e18ae133ea02a9405093b652eb4921ca62c3114343561a2d8067f2be6d9df8dd32d10e30d3ce58150f795600680e6230fb136681 +"@sentry/types@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/types@npm:7.77.0" + checksum: ff8047425d4b0caae9221a9fa52fa0122ddf23b51152b7d71914f6465cf72dc3fdf00b7db16e3e95b4a1d2d429824d91ed586b1abac6d5286bd7800504e30824 languageName: node linkType: hard -"@sentry/utils@npm:7.53.1": - version: 7.53.1 - resolution: "@sentry/utils@npm:7.53.1" +"@sentry/utils@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/utils@npm:7.77.0" dependencies: - "@sentry/types": 7.53.1 - tslib: ^1.9.3 - checksum: d9556f161a0eed0e1bb279bac12b99492b6212d9b6cabebbcb2d0f196f99b96c319305ec347d8f06382e2dde5782d5ba20a00f762fbfd4e8699e9100ef921804 + "@sentry/types": 7.77.0 + checksum: 000bb1caec44812dc39a8026ecba78c14a8332f15cb78d2935c91a7ba8d61eab9553a791a4af528a113ca90b83db6e81791e3562b2559b76596accc69e4d57ad + languageName: node + linkType: hard + +"@sentry/vercel-edge@npm:7.77.0": + version: 7.77.0 + resolution: "@sentry/vercel-edge@npm:7.77.0" + dependencies: + "@sentry/core": 7.77.0 + "@sentry/types": 7.77.0 + "@sentry/utils": 7.77.0 + checksum: 3c23c46af233fe3d50f48509fd8e89c24be54d79419e0368bbcc574b6f1d886ff182300e507bbe5fb587c1219ee9663eaa1591783ca0a7bbebdd85b8b35a07ad languageName: node linkType: hard @@ -18555,7 +18561,7 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.4.2, cookie@npm:^0.4.1, cookie@npm:^0.4.2": +"cookie@npm:0.4.2, cookie@npm:^0.4.2": version: 0.4.2 resolution: "cookie@npm:0.4.2" checksum: a00833c998bedf8e787b4c342defe5fa419abd96b32f4464f718b91022586b8f1bafbddd499288e75c037642493c83083da426c6a9080d309e3bd90fd11baa9b @@ -28217,13 +28223,6 @@ __metadata: languageName: node linkType: hard -"lru_map@npm:^0.3.3": - version: 0.3.3 - resolution: "lru_map@npm:0.3.3" - checksum: ca9dd43c65ed7a4f117c548028101c5b6855e10923ea9d1f635af53ad20c5868ff428c364d454a7b57fe391b89c704982275410c3c5099cca5aeee00d76e169a - languageName: node - linkType: hard - "ltgt@npm:^2.1.2": version: 2.2.1 resolution: "ltgt@npm:2.2.1" @@ -35232,6 +35231,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:1.22.8, resolve@npm:^1.22.2": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c + languageName: node + linkType: hard + "resolve@npm:^1.1.7, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.3.2": version: 1.22.1 resolution: "resolve@npm:1.22.1" @@ -35258,19 +35270,6 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.2": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" - dependencies: - is-core-module: ^2.13.0 - path-parse: ^1.0.7 - supports-preserve-symlinks-flag: ^1.0.0 - bin: - resolve: bin/resolve - checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c - languageName: node - linkType: hard - "resolve@npm:^2.0.0-next.3": version: 2.0.0-next.3 resolution: "resolve@npm:2.0.0-next.3" @@ -35294,6 +35293,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@1.22.8#~builtin, resolve@patch:resolve@^1.22.2#~builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847 + languageName: node + linkType: hard + "resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.3.2#~builtin": version: 1.22.1 resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=c3c19d" @@ -35320,19 +35332,6 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.22.2#~builtin": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=c3c19d" - dependencies: - is-core-module: ^2.13.0 - path-parse: ^1.0.7 - supports-preserve-symlinks-flag: ^1.0.0 - bin: - resolve: bin/resolve - checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847 - languageName: node - linkType: hard - "resolve@patch:resolve@^2.0.0-next.3#~builtin": version: 2.0.0-next.3 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.3#~builtin::version=2.0.0-next.3&hash=c3c19d"