diff --git a/apps/api/lib/validations/event-type.ts b/apps/api/lib/validations/event-type.ts index 295884ab46..70823213bf 100644 --- a/apps/api/lib/validations/event-type.ts +++ b/apps/api/lib/validations/event-type.ts @@ -24,6 +24,11 @@ const hostSchema = _HostModel.pick({ userId: true, }); +export const childrenSchema = z.object({ + id: z.number().int(), + userId: z.number().int(), +}); + export const schemaEventTypeBaseBodyParams = EventType.pick({ title: true, description: true, @@ -45,6 +50,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({ disableGuests: true, hideCalendarNotes: true, minimumBookingNotice: true, + parentId: true, beforeEventBuffer: true, afterEventBuffer: true, teamId: true, @@ -56,7 +62,12 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({ bookingLimits: true, durationLimits: true, }) - .merge(z.object({ hosts: z.array(hostSchema).optional().default([]) })) + .merge( + z.object({ + children: z.array(childrenSchema).optional().default([]), + hosts: z.array(hostSchema).optional().default([]), + }) + ) .partial() .strict(); @@ -73,6 +84,7 @@ const schemaEventTypeCreateParams = z seatsShowAvailabilityCount: z.boolean().optional(), bookingFields: eventTypeBookingFields.optional(), scheduleId: z.number().optional(), + parentId: z.number().optional(), }) .strict(); @@ -125,6 +137,7 @@ export const schemaEventTypeReadPublic = EventType.pick({ price: true, currency: true, slotInterval: true, + parentId: true, successRedirectUrl: true, description: true, locations: true, @@ -137,6 +150,8 @@ export const schemaEventTypeReadPublic = EventType.pick({ durationLimits: true, }).merge( z.object({ + children: z.array(childrenSchema).optional().default([]), + hosts: z.array(hostSchema).optional().default([]), locations: z .array( z.object({ diff --git a/apps/api/pages/api/event-types/[id]/_get.ts b/apps/api/pages/api/event-types/[id]/_get.ts index 59c3c06786..b4de59b51a 100644 --- a/apps/api/pages/api/event-types/[id]/_get.ts +++ b/apps/api/pages/api/event-types/[id]/_get.ts @@ -52,6 +52,7 @@ export async function getHandler(req: NextApiRequest) { team: { select: { slug: true } }, users: true, owner: { select: { username: true, id: true } }, + children: { select: { id: true, userId: true } }, }, }); await checkPermissions(req, eventType); diff --git a/apps/api/pages/api/event-types/_get.ts b/apps/api/pages/api/event-types/_get.ts index 66d84cf621..a58e6422ce 100644 --- a/apps/api/pages/api/event-types/_get.ts +++ b/apps/api/pages/api/event-types/_get.ts @@ -46,6 +46,7 @@ async function getHandler(req: NextApiRequest) { team: { select: { slug: true } }, users: true, owner: { select: { username: true, id: true } }, + children: { select: { id: true, userId: true } }, }, }); // this really should return [], but backwards compatibility.. diff --git a/apps/api/pages/api/event-types/_post.ts b/apps/api/pages/api/event-types/_post.ts index 852b20158f..075ed4c71a 100644 --- a/apps/api/pages/api/event-types/_post.ts +++ b/apps/api/pages/api/event-types/_post.ts @@ -6,7 +6,9 @@ import { defaultResponder } from "@calcom/lib/server"; import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type"; +import checkParentEventOwnership from "./_utils/checkParentEventOwnership"; import checkTeamEventEditPermission from "./_utils/checkTeamEventEditPermission"; +import checkUserMembership from "./_utils/checkUserMembership"; import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts"; /** @@ -118,10 +120,13 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts"; * schedulingType: * type: string * description: The type of scheduling if a Team event. Required for team events only - * enum: [ROUND_ROBIN, COLLECTIVE] + * enum: [ROUND_ROBIN, COLLECTIVE, MANAGED] * price: * type: integer * description: Price of the event type booking + * parentId: + * type: integer + * description: EventTypeId of the parent managed event * currency: * type: string * description: Currency acronym. Eg- usd, eur, gbp, etc. @@ -276,6 +281,11 @@ async function postHandler(req: NextApiRequest) { await checkPermissions(req); + if (parsedBody.parentId) { + await checkParentEventOwnership(parsedBody.parentId, userId); + await checkUserMembership(parsedBody.parentId, parsedBody.userId); + } + if (isAdmin && parsedBody.userId) { data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } }; } diff --git a/apps/api/pages/api/event-types/_utils/checkParentEventOwnership.ts b/apps/api/pages/api/event-types/_utils/checkParentEventOwnership.ts new file mode 100644 index 0000000000..15ada70097 --- /dev/null +++ b/apps/api/pages/api/event-types/_utils/checkParentEventOwnership.ts @@ -0,0 +1,52 @@ +import { HttpError } from "@calcom/lib/http-error"; + +/** + * Checks if a user, identified by the provided userId, has ownership (or admin rights) over + * the team associated with the event type identified by the parentId. + * + * @param parentId - The ID of the parent event type. + * @param userId - The ID of the user. + * + * @throws {HttpError} If the parent event type is not found, + * if the parent event type doesn't belong to any team, + * or if the user doesn't have ownership or admin rights to the associated team. + */ +export default async function checkParentEventOwnership(parentId: number, userId: number) { + const parentEventType = await prisma.eventType.findUnique({ + where: { + id: parentId, + }, + select: { + teamId: true, + }, + }); + + if (!parentEventType) { + throw new HttpError({ + statusCode: 404, + message: "Parent event type not found.", + }); + } + + if (!parentEventType.teamId) { + throw new HttpError({ + statusCode: 400, + message: "This event type is not capable of having children", + }); + } + + const teamMember = await prisma.membership.findFirst({ + where: { + teamId: parentEventType.teamId, + userId: userId, + OR: [{ role: "OWNER" }, { role: "ADMIN" }], + }, + }); + + if (!teamMember) { + throw new HttpError({ + statusCode: 403, + message: "User is not authorized to access the team to which the parent event type belongs.", + }); + } +} diff --git a/apps/api/pages/api/event-types/_utils/checkUserMembership.ts b/apps/api/pages/api/event-types/_utils/checkUserMembership.ts new file mode 100644 index 0000000000..df819bc95e --- /dev/null +++ b/apps/api/pages/api/event-types/_utils/checkUserMembership.ts @@ -0,0 +1,52 @@ +import { HttpError } from "@calcom/lib/http-error"; + +/** + * Checks if a user, identified by the provided userId, is a member of the team associated + * with the event type identified by the parentId. + * + * @param parentId - The ID of the event type. + * @param userId - The ID of the user. + * + * @throws {HttpError} If the event type is not found, + * if the event type doesn't belong to any team, + * or if the user isn't a member of the associated team. + */ +export default async function checkUserMembership(parentId: number, userId: number) { + const parentEventType = await prisma.eventType.findUnique({ + where: { + id: parentId, + }, + select: { + teamId: true, + }, + }); + + if (!parentEventType) { + throw new HttpError({ + statusCode: 404, + message: "Event type not found.", + }); + } + + if (!parentEventType.teamId) { + throw new HttpError({ + statusCode: 400, + message: "This event type is not capable of having children.", + }); + } + + const teamMember = await prisma.membership.findFirst({ + where: { + teamId: parentEventType.teamId, + userId: userId, + accepted: true, + }, + }); + + if (!teamMember) { + throw new HttpError({ + statusCode: 400, + message: "User is not a team member.", + }); + } +} diff --git a/apps/api/pages/api/slots/_get.ts b/apps/api/pages/api/slots/_get.ts index a74d3ae83f..ffa3b83d74 100644 --- a/apps/api/pages/api/slots/_get.ts +++ b/apps/api/pages/api/slots/_get.ts @@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server"; import { createContext } from "@calcom/trpc/server/createContext"; -import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router"; +import { slotsRouter } from "@calcom/trpc/server/routers/viewer/slots/_router"; import { TRPCError } from "@trpc/server"; import { getHTTPStatusCodeFromError } from "@trpc/server/http"; @@ -11,10 +11,10 @@ import { getHTTPStatusCodeFromError } from "@trpc/server/http"; async function handler(req: NextApiRequest, res: NextApiResponse) { /** @see https://trpc.io/docs/server-side-calls */ const ctx = await createContext({ req, res }); - const caller = viewerRouter.createCaller(ctx); + const caller = slotsRouter.createCaller(ctx); try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - return await caller.slots.getSchedule(req.query as any /* Let tRPC handle this */); + return await caller.getSchedule(req.query as any /* Let tRPC handle this */); } catch (cause) { if (cause instanceof TRPCError) { const statusCode = getHTTPStatusCodeFromError(cause); diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx index fbff6af59a..3df736b876 100644 --- a/apps/web/components/eventtype/EventSetupTab.tsx +++ b/apps/web/components/eventtype/EventSetupTab.tsx @@ -294,7 +294,6 @@ export const EventSetupTab = ( const eventLabel = location[eventLocationType.defaultValueVariable] || t(eventLocationType.label); - return (
  • ) : ( <> + ) { try { /** @see https://trpc.io/docs/server-side-calls */ const ctx = await createContext({ req, res }, sessionGetter); - const caller = viewerRouter.createCaller({ + const caller = bookingsRouter.createCaller({ ...ctx, req, res, user: { ...user, locale: user?.locale ?? "en" }, }); - await caller.bookings.confirm({ + await caller.confirm({ bookingId: booking.id, recurringEventId: booking.recurringEventId || undefined, confirmed: action === DirectAction.ACCEPT, diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx index b07f79e00a..66577403ca 100644 --- a/apps/web/pages/booking/[uid].tsx +++ b/apps/web/pages/booking/[uid].tsx @@ -146,7 +146,7 @@ export default function Success(props: SuccessProps) { const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed; const [calculatedDuration, setCalculatedDuration] = useState(undefined); - + const { requiresLoginToUpdate } = props; function setIsCancellationMode(value: boolean) { const _searchParams = new URLSearchParams(searchParams); @@ -532,7 +532,28 @@ export default function Success(props: SuccessProps) { })} - {(!needsConfirmation || !userIsOwner) && + {requiresLoginToUpdate && ( + <> +
    +
    + {t("need_to_make_a_change")} + {/* Login button but redirect to here */} + + + + {t("login")} + + + +
    + + )} + {!requiresLoginToUpdate && + (!needsConfirmation || !userIsOwner) && !isCancelled && (!isCancellationMode ? ( <> @@ -540,28 +561,30 @@ export default function Success(props: SuccessProps) {
    {t("need_to_make_a_change")} - {!props.recurringBookings && ( - - - - {t("reschedule")} - + <> + {!props.recurringBookings && ( + + + + {t("reschedule")} + + + {t("or_lowercase")} - {t("or_lowercase")} - - )} - - + + +
    ) : ( @@ -1010,7 +1033,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { const session = await getServerSession(context); let tz: string | null = null; let userTimeFormat: number | null = null; - + let requiresLoginToUpdate = false; if (session) { const user = await ssr.viewer.me.fetch(); tz = user.timeZone; @@ -1022,9 +1045,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { if (!parsedQuery.success) return { notFound: true }; const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data; + const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid); const bookingInfoRaw = await prisma.booking.findFirst({ where: { - uid: await maybeGetBookingUidFromSeat(prisma, uid), + uid: maybeUid, }, select: { title: true, @@ -1088,6 +1112,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }; } + if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) { + requiresLoginToUpdate = true; + } + const bookingInfo = getBookingWithResponses(bookingInfoRaw); // @NOTE: had to do this because Server side cant return [Object objects] // probably fixable with json.stringify -> json.parse @@ -1156,6 +1184,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { paymentStatus: payment, ...(tz && { tz }), userTimeFormat, + requiresLoginToUpdate, }, }; } diff --git a/apps/web/pages/d/[link]/[slug].tsx b/apps/web/pages/d/[link]/[slug].tsx index f70f0b6a34..0b715ca5b9 100644 --- a/apps/web/pages/d/[link]/[slug].tsx +++ b/apps/web/pages/d/[link]/[slug].tsx @@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next"; import { z } from "zod"; import { Booker } from "@calcom/atoms"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses"; import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking"; @@ -27,6 +28,7 @@ export default function Type({ isTeamEvent, entity, duration, + hashedLink, }: PageProps) { return (
    @@ -46,6 +48,7 @@ export default function Type({ isTeamEvent={isTeamEvent} entity={entity} duration={duration} + hashedLink={hashedLink} />
    ); @@ -55,6 +58,7 @@ Type.PageWrapper = PageWrapper; Type.isBookingPage = true; async function getUserPageProps(context: GetServerSidePropsContext) { + const session = await getServerSession(context); const { link, slug } = paramsSchema.parse(context.params); const { rescheduleUid, duration: queryDuration } = context.query; const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? ""); @@ -117,7 +121,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { let booking: GetBookingType | null = null; if (rescheduleUid) { - booking = await getBookingForReschedule(`${rescheduleUid}`); + booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); } const isTeamEvent = !!hashedLink.eventType?.team?.id; @@ -149,6 +153,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { // Sending the team event from the server, because this template file // is reused for both team and user events. isTeamEvent, + hashedLink: link, }, }; } diff --git a/apps/web/pages/reschedule/[uid].tsx b/apps/web/pages/reschedule/[uid].tsx index fd07ae5f61..fbfa33d963 100644 --- a/apps/web/pages/reschedule/[uid].tsx +++ b/apps/web/pages/reschedule/[uid].tsx @@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next"; import { URLSearchParams } from "url"; import { z } from "zod"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getDefaultEvent } from "@calcom/lib/defaultEvents"; import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; @@ -12,11 +13,16 @@ export default function Type() { } export async function getServerSideProps(context: GetServerSidePropsContext) { - const { uid: bookingId, seatReferenceUid } = z + const session = await getServerSession(context); + + const { uid: bookingUid, seatReferenceUid } = z .object({ uid: z.string(), seatReferenceUid: z.string().optional() }) .parse(context.query); - const uid = await maybeGetBookingUidFromSeat(prisma, bookingId); + const { uid, seatReferenceUid: maybeSeatReferenceUid } = await maybeGetBookingUidFromSeat( + prisma, + bookingUid + ); const booking = await prisma.booking.findUnique({ where: { uid, @@ -37,6 +43,21 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }, }, seatsPerTimeSlot: true, + userId: true, + owner: { + select: { + id: true, + }, + }, + hosts: { + select: { + user: { + select: { + id: true, + }, + }, + }, + }, }, }, dynamicEventSlugRef: true, @@ -53,7 +74,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { } if (!booking?.eventType && !booking?.dynamicEventSlugRef) { - // TODO: Show something in UI to let user know that this booking is not rescheduleable. + // TODO: Show something in UI to let user know that this booking is not rescheduleable return { notFound: true, } as { @@ -61,6 +82,33 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }; } + // if booking event type is for a seated event and no seat reference uid is provided, throw not found + if (booking?.eventType?.seatsPerTimeSlot && !maybeSeatReferenceUid) { + const userId = session?.user?.id; + + if (!userId && !seatReferenceUid) { + return { + redirect: { + destination: `/auth/login?callbackUrl=/reschedule/${bookingUid}`, + permanent: false, + }, + }; + } + const userIsHost = booking?.eventType.hosts.find((host) => { + if (host.user.id === userId) return true; + }); + + const userIsOwnerOfEventType = booking?.eventType.owner?.id === userId; + + if (!userIsHost && !userIsOwnerOfEventType) { + return { + notFound: true, + } as { + notFound: true; + }; + } + } + const eventType = booking.eventType ? booking.eventType : getDefaultEvent(dynamicEventSlugRef); const eventPage = `${ @@ -72,7 +120,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }/${eventType?.slug}`; const destinationUrl = new URLSearchParams(); - destinationUrl.set("rescheduleUid", seatReferenceUid || bookingId); + destinationUrl.set("rescheduleUid", seatReferenceUid || bookingUid); return { redirect: { diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 2adbfe768a..41f4046447 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -5,6 +5,7 @@ import { usePathname } from "next/navigation"; import { useEffect } from "react"; import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; +import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; @@ -43,6 +44,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain } const teamName = team.name || "Nameless Team"; const isBioEmpty = !team.bio || !team.bio.replace("


    ", "").length; const metadata = teamMetadataSchema.parse(team.metadata); + const orgBranding = useOrgBranding(); useEffect(() => { telemetry.event( @@ -124,12 +126,6 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
  • -
    {ch.name} @@ -185,9 +181,11 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
    diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index c94247646e..668a82d6f8 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -2,6 +2,7 @@ import type { GetServerSidePropsContext } from "next"; import { z } from "zod"; import { Booker } from "@calcom/atoms"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses"; import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking"; @@ -37,6 +38,7 @@ export default function Type({ hideBranding={isBrandingHidden} isTeamEvent entity={entity} + bookingData={booking} /> { + const session = await getServerSession(context); const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params); const { rescheduleUid, duration: queryDuration } = context.query; const { ssrInit } = await import("@server/lib/ssr"); @@ -92,7 +95,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => let booking: GetBookingType | null = null; if (rescheduleUid) { - booking = await getBookingForReschedule(`${rescheduleUid}`); + booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id); } const org = isValidOrgDomain ? currentOrgDomain : null; diff --git a/apps/web/pages/video/[uid].tsx b/apps/web/pages/video/[uid].tsx index f14fed2213..57ca82780c 100644 --- a/apps/web/pages/video/[uid].tsx +++ b/apps/web/pages/video/[uid].tsx @@ -46,7 +46,7 @@ export default function JoinCall(props: JoinCallPageProps) { baseText: "#FFF", border: "#292929", mainAreaBg: "#111111", - mainAreaBgAccent: "#111111", + mainAreaBgAccent: "#1A1A1A", mainAreaText: "#FFF", supportiveText: "#FFF", }, diff --git a/apps/web/playwright/booking-seats.e2e.ts b/apps/web/playwright/booking-seats.e2e.ts index c46e84d4d2..c291f2d111 100644 --- a/apps/web/playwright/booking-seats.e2e.ts +++ b/apps/web/playwright/booking-seats.e2e.ts @@ -2,6 +2,7 @@ import { expect } from "@playwright/test"; import { uuid } from "short-uuid"; import { v4 as uuidv4 } from "uuid"; +import { randomString } from "@calcom/lib/random"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -96,7 +97,7 @@ test.describe("Booking with Seats", () => { }); test(`Attendees can cancel a seated event time slot`, async ({ page, users, bookings }) => { - const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ + const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ { name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" }, { name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" }, { name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" }, @@ -143,6 +144,19 @@ test.describe("Booking with Seats", () => { expect(attendeeIds).not.toContain(bookingAttendees[0].id); }); + await test.step("Attendee #2 shouldn't be able to cancel booking using only booking/uid", async () => { + await page.goto(`/booking/${booking.uid}`); + + await expect(page.locator("[text=Cancel]")).toHaveCount(0); + }); + + await test.step("Attendee #2 shouldn't be able to cancel booking using randomString for seatReferenceUId", async () => { + await page.goto(`/booking/${booking.uid}?seatReferenceUid=${randomString(10)}`); + + // expect cancel button to don't be in the page + await expect(page.locator("[text=Cancel]")).toHaveCount(0); + }); + await test.step("All attendees cancelling should delete the booking for the user", async () => { // The remaining 2 attendees cancel for (let i = 1; i < bookingSeats.length; i++) { @@ -166,6 +180,47 @@ test.describe("Booking with Seats", () => { expect(updatedBooking?.status).toBe(BookingStatus.CANCELLED); }); }); + + test("Owner shouldn't be able to cancel booking without login in", async ({ page, bookings, users }) => { + const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ + { name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" }, + ]); + await page.goto(`/booking/${booking.uid}?cancel=true`); + await expect(page.locator("[text=Cancel]")).toHaveCount(0); + + // expect login text to be in the page, not data-testid + await expect(page.locator("text=Login")).toHaveCount(1); + + // click on login button text + await page.locator("text=Login").click(); + + // expect to be redirected to login page with query parameter callbackUrl + await expect(page).toHaveURL(/\/auth\/login\?callbackUrl=.*/); + + await user.apiLogin(); + + // manual redirect to booking page + await page.goto(`/booking/${booking.uid}?cancel=true`); + + // expect login button to don't be in the page + await expect(page.locator("text=Login")).toHaveCount(0); + + // fill reason for cancellation + await page.fill('[data-testid="cancel_reason"]', "Double booked!"); + + // confirm cancellation + await page.locator('[data-testid="confirm_cancel"]').click(); + await page.waitForLoadState("networkidle"); + + const updatedBooking = await prisma.booking.findFirst({ + where: { id: booking.id }, + }); + + expect(updatedBooking).not.toBeNull(); + expect(updatedBooking?.status).toBe(BookingStatus.CANCELLED); + }); }); test.describe("Reschedule for booking with seats", () => { @@ -543,4 +598,113 @@ test.describe("Reschedule for booking with seats", () => { .first(); await expect(foundFirstAttendeeAgain).toHaveCount(1); }); + + test("Owner shouldn't be able to reschedule booking without login in", async ({ + page, + bookings, + users, + }) => { + const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ + { name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" }, + ]); + const getBooking = await booking.self(); + + await page.goto(`/booking/${booking.uid}`); + await expect(page.locator('[data-testid="reschedule"]')).toHaveCount(0); + + // expect login text to be in the page, not data-testid + await expect(page.locator("text=Login")).toHaveCount(1); + + // click on login button text + await page.locator("text=Login").click(); + + // expect to be redirected to login page with query parameter callbackUrl + await expect(page).toHaveURL(/\/auth\/login\?callbackUrl=.*/); + + await user.apiLogin(); + + // manual redirect to booking page + await page.goto(`/booking/${booking.uid}`); + + // expect login button to don't be in the page + await expect(page.locator("text=Login")).toHaveCount(0); + + // reschedule-link click + await page.locator('[data-testid="reschedule-link"]').click(); + + await selectFirstAvailableTimeSlotNextMonth(page); + + // data displayed in form should be user owner + const nameElement = await page.locator("input[name=name]"); + const name = await nameElement.inputValue(); + expect(name).toBe(user.username); + + //same for email + const emailElement = await page.locator("input[name=email]"); + const email = await emailElement.inputValue(); + expect(email).toBe(user.email); + + // reason to reschedule input should be visible textfield with name rescheduleReason + const reasonElement = await page.locator("textarea[name=rescheduleReason]"); + await expect(reasonElement).toBeVisible(); + + // expect to be redirected to reschedule page + await page.locator('[data-testid="confirm-reschedule-button"]').click(); + + // should wait for URL but that path starts with booking/ + await page.waitForURL(/\/booking\/.*/); + + await expect(page).toHaveURL(/\/booking\/.*/); + + await page.waitForLoadState("networkidle"); + + const updatedBooking = await prisma.booking.findFirst({ + where: { id: booking.id }, + }); + + expect(updatedBooking).not.toBeNull(); + expect(getBooking?.startTime).not.toBe(updatedBooking?.startTime); + expect(getBooking?.endTime).not.toBe(updatedBooking?.endTime); + expect(updatedBooking?.status).toBe(BookingStatus.ACCEPTED); + }); + + test("Owner shouldn't be able to reschedule when going directly to booking/rescheduleUid", async ({ + page, + bookings, + users, + }) => { + const { booking, user } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [ + { name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "Jane Second", email: "second+seats@cal.com", timeZone: "Europe/Berlin" }, + { name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" }, + ]); + const getBooking = await booking.self(); + + await page.goto(`/${user.username}/seats?rescheduleUid=${getBooking?.uid}&bookingUid=null`); + + await selectFirstAvailableTimeSlotNextMonth(page); + + // expect textarea with name notes to be visible + const notesElement = await page.locator("textarea[name=notes]"); + await expect(notesElement).toBeVisible(); + + // expect button confirm instead of reschedule + await expect(page.locator('[data-testid="confirm-book-button"]')).toHaveCount(1); + + // now login and try again + await user.apiLogin(); + + await page.goto(`/${user.username}/seats?rescheduleUid=${getBooking?.uid}&bookingUid=null`); + + await selectFirstAvailableTimeSlotNextMonth(page); + + await expect(page).toHaveTitle(/(?!.*reschedule).*/); + + // expect button reschedule + await expect(page.locator('[data-testid="confirm-reschedule-button"]')).toHaveCount(1); + }); + + // @TODO: force 404 when rescheduleUid is not found }); diff --git a/apps/web/public/map-pin.svg b/apps/web/public/map-pin-dark.svg similarity index 100% rename from apps/web/public/map-pin.svg rename to apps/web/public/map-pin-dark.svg diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 2491c787c4..16b1cb13a4 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1535,6 +1535,7 @@ "problem_registering_domain": "There was a problem with registering the subdomain, please try again or contact an administrator", "team_publish": "Publish team", "number_text_notifications": "Phone number (Text notifications)", + "number_sms_notifications": "Phone number (SMS notifications)", "attendee_email_variable": "Attendee email", "attendee_email_info": "The person booking's email", "kbar_search_placeholder": "Type a command or search...", diff --git a/apps/web/public/static/locales/fr/common.json b/apps/web/public/static/locales/fr/common.json index eb8964951e..2eb3b98328 100644 --- a/apps/web/public/static/locales/fr/common.json +++ b/apps/web/public/static/locales/fr/common.json @@ -1530,6 +1530,7 @@ "problem_registering_domain": "Un problème est survenu lors de l'enregistrement du sous-domaine, veuillez réessayer ou contacter un administrateur", "team_publish": "Publier l'équipe", "number_text_notifications": "Numéro de téléphone (notifications par SMS)", + "number_sms_notifications": "Numéro de téléphone (notifications par SMS)", "attendee_email_variable": "Adresse e-mail du participant", "attendee_email_info": "Adresse e-mail du participant", "kbar_search_placeholder": "Saisissez une commande ou une recherche...", diff --git a/apps/web/public/static/locales/pt/common.json b/apps/web/public/static/locales/pt/common.json index 5379480f75..cfc18fa0c1 100644 --- a/apps/web/public/static/locales/pt/common.json +++ b/apps/web/public/static/locales/pt/common.json @@ -1530,6 +1530,7 @@ "problem_registering_domain": "Houve um problema ao registar o subdomínio. Tente novamente ou contacte um administrador", "team_publish": "Publicar equipa", "number_text_notifications": "Número de telefone (notificações de texto)", + "number_sms_notifications": "Número de telefone (notificações SMS)", "attendee_email_variable": "E-mail do participante", "attendee_email_info": "O e-mail do responsável pela reserva", "kbar_search_placeholder": "Digite um comando ou pesquise...", diff --git a/packages/app-store/cal-ai/api/_getAdd.ts b/packages/app-store/cal-ai/api/_getAdd.ts index b263e28768..9928f1a72f 100644 --- a/packages/app-store/cal-ai/api/_getAdd.ts +++ b/packages/app-store/cal-ai/api/_getAdd.ts @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { defaultResponder } from "@calcom/lib/server"; import { createContext } from "@calcom/trpc/server/createContext"; -import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router"; +import { apiKeysRouter } from "@calcom/trpc/server/routers/viewer/apiKeys/_router"; import checkSession from "../../_utils/auth"; import getInstalledAppPath from "../../_utils/getInstalledAppPath"; @@ -15,9 +15,9 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) { const appType = appConfig.type; const ctx = await createContext({ req, res }); - const caller = viewerRouter.createCaller(ctx); + const caller = apiKeysRouter.createCaller(ctx); - const apiKey = await caller.apiKeys.create({ + const apiKey = await caller.create({ note: "Cal.ai", expiresAt: null, appId: "cal-ai", diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts index 12c5152396..fa7dec8e7a 100644 --- a/packages/app-store/googlecalendar/lib/CalendarService.ts +++ b/packages/app-store/googlecalendar/lib/CalendarService.ts @@ -284,9 +284,10 @@ export default class GoogleCalendarService implements Calendar { const calendar = await this.authedCalendar(); - const selectedCalendar = externalCalendarId - ? externalCalendarId - : event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId; + const selectedCalendar = + (externalCalendarId + ? event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId + : undefined) || "primary"; try { const evt = await calendar.events.update({ @@ -337,14 +338,15 @@ export default class GoogleCalendarService implements Calendar { async deleteEvent(uid: string, event: CalendarEvent, externalCalendarId?: string | null): Promise { const calendar = await this.authedCalendar(); - const defaultCalendarId = "primary"; - const calendarId = externalCalendarId - ? externalCalendarId - : event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId; + + const selectedCalendar = + (externalCalendarId + ? event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.externalId + : undefined) || "primary"; try { const event = await calendar.events.delete({ - calendarId: calendarId ? calendarId : defaultCalendarId, + calendarId: selectedCalendar, eventId: uid, sendNotifications: false, sendUpdates: "none", diff --git a/packages/app-store/locations.ts b/packages/app-store/locations.ts index 019a53507a..68358d839f 100644 --- a/packages/app-store/locations.ts +++ b/packages/app-store/locations.ts @@ -91,7 +91,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ attendeeInputType: "attendeeAddress", attendeeInputPlaceholder: "enter_address", defaultValueVariable: "attendeeAddress", - iconUrl: "/map-pin.svg", + iconUrl: "/map-pin-dark.svg", category: "in person", }, { @@ -103,7 +103,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ // HACK: variable: "locationAddress", defaultValueVariable: "address", - iconUrl: "/map-pin.svg", + iconUrl: "/map-pin-dark.svg", category: "in person", }, { diff --git a/packages/features/bookings/Booker/Booker.tsx b/packages/features/bookings/Booker/Booker.tsx index d8995856ff..622da4cf77 100644 --- a/packages/features/bookings/Booker/Booker.tsx +++ b/packages/features/bookings/Booker/Booker.tsx @@ -44,6 +44,7 @@ const BookerComponent = ({ isTeamEvent, entity, duration, + hashedLink, }: BookerProps) => { /** * Prioritize dateSchedule load @@ -285,6 +286,7 @@ const BookerComponent = ({ setSeatedEventData({ ...seatedEventData, bookingUid: undefined, attendees: undefined }); } }} + hashedLink={hashedLink} /> diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx index 7c12ff9478..eda9cb262c 100644 --- a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx +++ b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx @@ -41,11 +41,12 @@ import { FormSkeleton } from "./Skeleton"; type BookEventFormProps = { onCancel?: () => void; + hashedLink?: string | null; }; type DefaultValues = Record; -export const BookEventForm = ({ onCancel }: BookEventFormProps) => { +export const BookEventForm = ({ onCancel, hashedLink }: BookEventFormProps) => { const [slotReservationId, setSlotReservationId] = useSlotReservationId(); const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation({ trpc: { @@ -114,6 +115,7 @@ export const BookEventForm = ({ onCancel }: BookEventFormProps) => { isRescheduling={isRescheduling} eventQuery={eventQuery} rescheduleUid={rescheduleUid} + hashedLink={hashedLink} /> ); }; @@ -124,11 +126,13 @@ export const BookEventFormChild = ({ isRescheduling, eventQuery, rescheduleUid, + hashedLink, }: BookEventFormProps & { initialValues: DefaultValues; isRescheduling: boolean; eventQuery: ReturnType; rescheduleUid: string | null; + hashedLink?: string | null; }) => { const eventType = eventQuery.data; const bookingFormSchema = z @@ -332,6 +336,7 @@ export const BookEventFormChild = ({ }), {} ), + hashedLink, }; if (eventQuery.data?.recurringEvent?.freq && recurringEventCount) { @@ -370,6 +375,7 @@ export const BookEventFormChild = ({ fields={eventType.bookingFields} locations={eventType.locations} rescheduleUid={rescheduleUid || undefined} + bookingData={bookingData} /> {(createBookingMutation.isError || createRecurringBookingMutation.isError || @@ -399,8 +405,8 @@ export const BookEventFormChild = ({ type="submit" color="primary" loading={createBookingMutation.isLoading || createRecurringBookingMutation.isLoading} - data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}> - {rescheduleUid + data-testid={rescheduleUid && bookingData ? "confirm-reschedule-button" : "confirm-book-button"}> + {rescheduleUid && bookingData ? t("reschedule") : renderConfirmNotVerifyEmailButtonCond ? t("confirm") @@ -492,12 +498,18 @@ function useInitialFormValues({ }); const defaultUserValues = { - email: rescheduleUid - ? bookingData?.attendees[0].email - : parsedQuery["email"] || session.data?.user?.email || "", - name: rescheduleUid - ? bookingData?.attendees[0].name - : parsedQuery["name"] || session.data?.user?.name || "", + email: + rescheduleUid && bookingData && bookingData.attendees.length > 0 + ? bookingData?.attendees[0].email + : !!parsedQuery["email"] + ? parsedQuery["email"] + : session.data?.user?.email ?? "", + name: + rescheduleUid && bookingData && bookingData.attendees.length > 0 + ? bookingData?.attendees[0].name + : !!parsedQuery["name"] + ? parsedQuery["name"] + : session.data?.user?.name ?? session.data?.user?.username ?? "", }; if (!isRescheduling) { @@ -521,14 +533,12 @@ function useInitialFormValues({ setDefaultValues(defaults); } - if ((!rescheduleUid && !bookingData) || !bookingData?.attendees.length) { - return {}; - } - const primaryAttendee = bookingData.attendees[0]; - if (!primaryAttendee) { + if (!rescheduleUid && !bookingData) { return {}; } + // We should allow current session user as default values for booking form + const defaults = { responses: {} as Partial>>, }; @@ -536,7 +546,7 @@ function useInitialFormValues({ const responses = eventType.bookingFields.reduce((responses, field) => { return { ...responses, - [field.name]: bookingData.responses[field.name], + [field.name]: bookingData?.responses[field.name], }; }, {}); defaults.responses = { diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx index d301891770..ab1505a787 100644 --- a/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx +++ b/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx @@ -1,6 +1,7 @@ import { useFormContext } from "react-hook-form"; import type { LocationObject } from "@calcom/app-store/locations"; +import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect"; import { FormBuilderField } from "@calcom/features/form-builder/FormBuilderField"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -13,10 +14,12 @@ export const BookingFields = ({ locations, rescheduleUid, isDynamicGroupBooking, + bookingData, }: { fields: NonNullable["bookingFields"]; locations: LocationObject[]; rescheduleUid?: string; + bookingData?: GetBookingType | null; isDynamicGroupBooking: boolean; }) => { const { t } = useLocale(); @@ -32,7 +35,9 @@ export const BookingFields = ({ // During reschedule by default all system fields are readOnly. Make them editable on case by case basis. // Allowing a system field to be edited might require sending emails to attendees, so we need to be careful let readOnly = - (field.editable === "system" || field.editable === "system-but-optional") && !!rescheduleUid; + (field.editable === "system" || field.editable === "system-but-optional") && + !!rescheduleUid && + bookingData !== null; let hidden = !!field.hidden; const fieldViews = field.views; @@ -42,6 +47,9 @@ export const BookingFields = ({ } if (field.name === SystemField.Enum.rescheduleReason) { + if (bookingData === null) { + return null; + } // rescheduleReason is a reschedule specific field and thus should be editable during reschedule readOnly = false; } @@ -64,8 +72,8 @@ export const BookingFields = ({ hidden = isDynamicGroupBooking ? true : !!field.hidden; } - // We don't show `notes` field during reschedule - if (field.name === SystemField.Enum.notes && !!rescheduleUid) { + // We don't show `notes` field during reschedule but since it's a query param we better valid if rescheduleUid brought any bookingData + if (field.name === SystemField.Enum.notes && bookingData !== null) { return null; } diff --git a/packages/features/bookings/Booker/types.ts b/packages/features/bookings/Booker/types.ts index dbbf9ec5c4..badfe667c8 100644 --- a/packages/features/bookings/Booker/types.ts +++ b/packages/features/bookings/Booker/types.ts @@ -64,6 +64,10 @@ export interface BookerProps { * otherwise, the default value is selected */ duration?: number | null; + /** + * Refers to the private link from event types page. + */ + hashedLink?: string | null; } export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking"; diff --git a/packages/features/bookings/components/BookerSeo.tsx b/packages/features/bookings/components/BookerSeo.tsx index b331b8fb9f..e11b872431 100644 --- a/packages/features/bookings/components/BookerSeo.tsx +++ b/packages/features/bookings/components/BookerSeo.tsx @@ -1,3 +1,4 @@ +import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { HeadSeo } from "@calcom/ui"; @@ -14,10 +15,20 @@ interface BookerSeoProps { teamSlug?: string | null; name?: string | null; }; + bookingData?: GetBookingType | null; } export const BookerSeo = (props: BookerSeoProps) => { - const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent, entity, isSEOIndexable } = props; + const { + eventSlug, + username, + rescheduleUid, + hideBranding, + isTeamEvent, + entity, + isSEOIndexable, + bookingData, + } = props; const { t } = useLocale(); const { data: event } = trpc.viewer.public.event.useQuery( { username, eventSlug, isTeamEvent, org: entity.orgSlug ?? null }, @@ -29,7 +40,7 @@ export const BookerSeo = (props: BookerSeoProps) => { const title = event?.title ?? ""; return ( 1 ? (
    map-pin diff --git a/packages/features/bookings/components/event-meta/Members.tsx b/packages/features/bookings/components/event-meta/Members.tsx index 1712666534..85d8f22d2c 100644 --- a/packages/features/bookings/components/event-meta/Members.tsx +++ b/packages/features/bookings/components/event-meta/Members.tsx @@ -61,7 +61,7 @@ export const EventMembers = ({ schedulingType, users, profile, entity }: EventMe image: "logo" in profile && profile.logo ? `${profile.logo}` : undefined, alt: profile.name || undefined, href: profile.username - ? `${CAL_URL}` + (pathname.indexOf("/team/") !== -1 ? "/team" : "") + `/${profile.username}` + ? `${CAL_URL}${pathname.indexOf("/team/") !== -1 ? "/team" : ""}/${profile.username}` : undefined, }); diff --git a/packages/features/bookings/lib/book-event-form/booking-to-mutation-input-mapper.tsx b/packages/features/bookings/lib/book-event-form/booking-to-mutation-input-mapper.tsx index f9ca1cf538..2abed19098 100644 --- a/packages/features/bookings/lib/book-event-form/booking-to-mutation-input-mapper.tsx +++ b/packages/features/bookings/lib/book-event-form/booking-to-mutation-input-mapper.tsx @@ -18,6 +18,7 @@ type BookingOptions = { metadata?: Record; bookingUid?: string; seatReferenceUid?: string; + hashedLink?: string | null; }; export const mapBookingToMutationInput = ({ @@ -32,6 +33,7 @@ export const mapBookingToMutationInput = ({ metadata, bookingUid, seatReferenceUid, + hashedLink, }: BookingOptions): BookingCreateBody => { return { ...values, @@ -47,11 +49,10 @@ export const mapBookingToMutationInput = ({ language: language, rescheduleUid, metadata: metadata || {}, - hasHashedBookingLink: false, + hasHashedBookingLink: hashedLink ? true : false, bookingUid, seatReferenceUid, - // hasHashedBookingLink, - // hashedLink, + hashedLink, }; }; diff --git a/packages/features/bookings/lib/get-booking.ts b/packages/features/bookings/lib/get-booking.ts index 350e6cfca6..bd7bcc727b 100644 --- a/packages/features/bookings/lib/get-booking.ts +++ b/packages/features/bookings/lib/get-booking.ts @@ -109,7 +109,7 @@ export const getBookingWithResponses = < export default getBooking; -export const getBookingForReschedule = async (uid: string) => { +export const getBookingForReschedule = async (uid: string, userId?: number) => { let rescheduleUid: string | null = null; const theBooking = await prisma.booking.findFirst({ where: { @@ -117,8 +117,25 @@ export const getBookingForReschedule = async (uid: string) => { }, select: { id: true, + userId: true, + eventType: { + select: { + seatsPerTimeSlot: true, + hosts: { + select: { + userId: true, + }, + }, + owner: { + select: { + id: true, + }, + }, + }, + }, }, }); + let bookingSeatReferenceUid: number | null = null; // If no booking is found via the uid, it's probably a booking seat // that its being rescheduled, which we query next. @@ -144,11 +161,26 @@ export const getBookingForReschedule = async (uid: string) => { }, }); if (bookingSeat) { + bookingSeatReferenceUid = bookingSeat.id; rescheduleUid = bookingSeat.booking.uid; attendeeEmail = bookingSeat.attendee.email; } } + // If we have the booking and not bookingSeat, we need to make sure the booking belongs to the userLoggedIn + // Otherwise, we return null here. + let hasOwnershipOnBooking = false; + if (theBooking && theBooking?.eventType?.seatsPerTimeSlot && bookingSeatReferenceUid === null) { + const isOwnerOfBooking = theBooking.userId === userId; + + const isHostOfEventType = theBooking?.eventType?.hosts.some((host) => host.userId === userId); + + const isUserIdInBooking = theBooking.userId === userId; + + if (!isOwnerOfBooking && !isHostOfEventType && !isUserIdInBooking) return null; + hasOwnershipOnBooking = true; + } + // If we don't have a booking and no rescheduleUid, the ID is invalid, // and we return null here. if (!theBooking && !rescheduleUid) return null; @@ -161,6 +193,8 @@ export const getBookingForReschedule = async (uid: string) => { ...booking, attendees: rescheduleUid ? booking.attendees.filter((attendee) => attendee.email === attendeeEmail) + : hasOwnershipOnBooking + ? [] : booking.attendees, }; }; diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 37758d3f34..924ae49436 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -136,6 +136,19 @@ async function handler(req: CustomRequest) { throw new HttpError({ statusCode: 400, message: "User not found" }); } + // If the booking is a seated event and there is no seatReferenceUid we should validate that logged in user is host + if (bookingToDelete.eventType?.seatsPerTimeSlot && !seatReferenceUid) { + const userIsHost = bookingToDelete.eventType.hosts.find((host) => { + if (host.user.id === userId) return true; + }); + + const userIsOwnerOfEventType = bookingToDelete.eventType.owner?.id === userId; + + if (!userIsHost && !userIsOwnerOfEventType) { + throw new HttpError({ statusCode: 401, message: "User not a host of this event" }); + } + } + // get webhooks const eventTrigger: WebhookTriggerEvents = "BOOKING_CANCELLED"; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index ed8a797d52..5475445fa2 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -996,7 +996,7 @@ async function handler( if (isTeamEventType && locationBodyString === OrganizerDefaultConferencingAppType) { const metadataParseResult = userMetadataSchema.safeParse(organizerUser.metadata); const organizerMetadata = metadataParseResult.success ? metadataParseResult.data : undefined; - if (organizerMetadata) { + if (organizerMetadata?.defaultConferencingApp?.appSlug) { const app = getAppFromSlug(organizerMetadata?.defaultConferencingApp?.appSlug); locationBodyString = app?.appData?.location?.type || locationBodyString; organizerOrFirstDynamicGroupMemberDefaultLocationUrl = diff --git a/packages/features/ee/teams/pages/team-profile-view.tsx b/packages/features/ee/teams/pages/team-profile-view.tsx index 9ae0e26cc4..d9cd6ab6e2 100644 --- a/packages/features/ee/teams/pages/team-profile-view.tsx +++ b/packages/features/ee/teams/pages/team-profile-view.tsx @@ -176,30 +176,37 @@ const ProfileView = () => { mutation.mutate({ id: team.id, ...variables }); } }}> -
    - ( - <> - -
    - { - form.setValue("logo", newLogo); - }} - imageSrc={value} - /> -
    - - )} - /> -
    - -
    + {!team.parent && ( + <> +
    + ( + <> + +
    + { + form.setValue("logo", newLogo); + }} + imageSrc={value} + /> +
    + + )} + /> +
    +
    + + )} )}
    - {team.name + {!team.parentId && ( + {team.name + )}

    {team.name}

    {!team.accepted && ( @@ -517,11 +519,13 @@ const SettingsSidebarContainer = ({ )}
    - {otherTeam.name + {!otherTeam.parentId && ( + {otherTeam.name + )}

    {otherTeam.name}

    diff --git a/packages/lib/server/maybeGetBookingUidFromSeat.ts b/packages/lib/server/maybeGetBookingUidFromSeat.ts index c211da5f4c..a6a4b8587c 100644 --- a/packages/lib/server/maybeGetBookingUidFromSeat.ts +++ b/packages/lib/server/maybeGetBookingUidFromSeat.ts @@ -15,6 +15,6 @@ export async function maybeGetBookingUidFromSeat(prisma: PrismaClient, uid: stri }, }, }); - if (bookingSeat) return bookingSeat.booking.uid; - return uid; + if (bookingSeat) return { uid: bookingSeat.booking.uid, seatReferenceUid: uid }; + return { uid }; } diff --git a/packages/ui/components/data-table/DataTableToolbar.tsx b/packages/ui/components/data-table/DataTableToolbar.tsx index 9e8250b3ba..df3370bed8 100644 --- a/packages/ui/components/data-table/DataTableToolbar.tsx +++ b/packages/ui/components/data-table/DataTableToolbar.tsx @@ -36,10 +36,10 @@ export function DataTableToolbar({ const isFiltered = table.getState().columnFilters.length > 0; return ( -
    +
    {searchKey && ( table.getColumn(searchKey)?.setFilterValue(event.target.value)} diff --git a/packages/ui/components/data-table/index.tsx b/packages/ui/components/data-table/index.tsx index 3cf83a7906..5a41ebcf4e 100644 --- a/packages/ui/components/data-table/index.tsx +++ b/packages/ui/components/data-table/index.tsx @@ -102,21 +102,9 @@ export function DataTable({ searchKey={searchKey} tableCTA={tableCTA} /> -
    +
    - + {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { diff --git a/packages/ui/components/table/TableNew.tsx b/packages/ui/components/table/TableNew.tsx index 0825ff375b..146e2297d5 100644 --- a/packages/ui/components/table/TableNew.tsx +++ b/packages/ui/components/table/TableNew.tsx @@ -4,7 +4,7 @@ import { classNames } from "@calcom/lib"; const Table = React.forwardRef>( ({ className, ...props }, ref) => ( -
    +
    ) @@ -13,7 +13,11 @@ Table.displayName = "Table"; const TableHeader = React.forwardRef>( ({ className, ...props }, ref) => ( - + ) ); TableHeader.displayName = "TableHeader"; diff --git a/yarn.lock b/yarn.lock index 6320449a24..e3be105d61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -91,6 +91,13 @@ __metadata: languageName: node linkType: hard +"@alloc/quick-lru@npm:^5.2.0": + version: 5.2.0 + resolution: "@alloc/quick-lru@npm:5.2.0" + checksum: bdc35758b552bcf045733ac047fb7f9a07c4678b944c641adfbd41f798b4b91fffd0fdc0df2578d9b0afc7b4d636aa6e110ead5d6281a2adc1ab90efd7f057f8 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.2.0": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" @@ -3183,7 +3190,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.21.0": +"@babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.21.0": version: 7.23.1 resolution: "@babel/runtime@npm:7.23.1" dependencies: @@ -3535,15 +3542,13 @@ __metadata: "@calcom/ui": "*" "@types/node": 16.9.1 "@types/react": 18.0.26 - "@types/react-dom": ^18.0.9 + "@types/react-dom": 18.0.9 eslint: ^8.34.0 eslint-config-next: ^13.2.1 - next: ^13.4.6 - next-auth: ^4.22.1 - postcss: ^8.4.18 + next: ^13.2.1 + next-auth: ^4.20.1 react: ^18.2.0 react-dom: ^18.2.0 - tailwindcss: ^3.3.1 typescript: ^4.9.4 languageName: unknown linkType: soft @@ -3637,7 +3642,7 @@ __metadata: "@calcom/ui": "*" "@headlessui/react": ^1.5.0 "@heroicons/react": ^1.0.6 - "@prisma/client": ^5.0.0 + "@prisma/client": ^4.13.0 "@tailwindcss/forms": ^0.5.2 "@types/node": 16.9.1 "@types/react": 18.0.26 @@ -3645,21 +3650,21 @@ __metadata: chart.js: ^3.7.1 client-only: ^0.0.1 eslint: ^8.34.0 - next: ^13.4.6 - next-auth: ^4.22.1 - next-i18next: ^13.2.2 + next: ^13.2.1 + next-auth: ^4.20.1 + next-i18next: ^11.3.0 postcss: ^8.4.18 - prisma: ^5.0.0 + prisma: ^4.13.0 prisma-field-encryption: ^1.4.0 react: ^18.2.0 react-chartjs-2: ^4.0.1 react-dom: ^18.2.0 react-hook-form: ^7.43.3 - react-live-chat-loader: ^2.8.1 + react-live-chat-loader: ^2.7.3 swr: ^1.2.2 - tailwindcss: ^3.3.1 + tailwindcss: ^3.2.1 typescript: ^4.9.4 - zod: ^3.22.2 + zod: ^3.20.2 languageName: unknown linkType: soft @@ -3991,6 +3996,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/intercom@workspace:packages/app-store/intercom": + version: 0.0.0-use.local + resolution: "@calcom/intercom@workspace:packages/app-store/intercom" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/jitsivideo@workspace:packages/app-store/jitsivideo": version: 0.0.0-use.local resolution: "@calcom/jitsivideo@workspace:packages/app-store/jitsivideo" @@ -4491,6 +4505,7 @@ __metadata: "@radix-ui/react-collapsible": ^1.0.0 "@radix-ui/react-dialog": ^1.0.4 "@radix-ui/react-dropdown-menu": ^2.0.5 + "@radix-ui/react-hover-card": ^1.0.7 "@radix-ui/react-id": ^1.0.0 "@radix-ui/react-popover": ^1.0.2 "@radix-ui/react-radio-group": ^1.0.0 @@ -7768,6 +7783,13 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:13.5.4": + version: 13.5.4 + resolution: "@next/env@npm:13.5.4" + checksum: 95ec7108bc88a01fed5389fb33e4b9eb34937908859d9f0aa87930c660f4395d90dafe10e54830faae5bc0a1b799be544c6455a2c8054499569d1e9296369076 + languageName: node + linkType: hard + "@next/eslint-plugin-next@npm:13.2.1": version: 13.2.1 resolution: "@next/eslint-plugin-next@npm:13.2.1" @@ -7784,6 +7806,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-arm64@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-darwin-arm64@npm:13.5.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-darwin-x64@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-darwin-x64@npm:13.4.6" @@ -7791,6 +7820,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-x64@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-darwin-x64@npm:13.5.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@next/swc-linux-arm64-gnu@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-linux-arm64-gnu@npm:13.4.6" @@ -7798,6 +7834,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-gnu@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-linux-arm64-gnu@npm:13.5.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-arm64-musl@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-linux-arm64-musl@npm:13.4.6" @@ -7805,6 +7848,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-musl@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-linux-arm64-musl@npm:13.5.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-x64-gnu@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-linux-x64-gnu@npm:13.4.6" @@ -7812,6 +7862,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-gnu@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-linux-x64-gnu@npm:13.5.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-x64-musl@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-linux-x64-musl@npm:13.4.6" @@ -7819,6 +7876,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-musl@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-linux-x64-musl@npm:13.5.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@next/swc-win32-arm64-msvc@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-win32-arm64-msvc@npm:13.4.6" @@ -7826,6 +7890,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-arm64-msvc@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-win32-arm64-msvc@npm:13.5.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-win32-ia32-msvc@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-win32-ia32-msvc@npm:13.4.6" @@ -7833,6 +7904,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-ia32-msvc@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-win32-ia32-msvc@npm:13.5.4" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@next/swc-win32-x64-msvc@npm:13.4.6": version: 13.4.6 resolution: "@next/swc-win32-x64-msvc@npm:13.4.6" @@ -7840,6 +7918,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-x64-msvc@npm:13.5.4": + version: 13.5.4 + resolution: "@next/swc-win32-x64-msvc@npm:13.5.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": version: 1.1.0 resolution: "@noble/curves@npm:1.1.0" @@ -8147,17 +8232,17 @@ __metadata: languageName: node linkType: hard -"@prisma/client@npm:^5.0.0": - version: 5.3.1 - resolution: "@prisma/client@npm:5.3.1" +"@prisma/client@npm:^4.13.0": + version: 4.16.2 + resolution: "@prisma/client@npm:4.16.2" dependencies: - "@prisma/engines-version": 5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59 + "@prisma/engines-version": 4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81 peerDependencies: prisma: "*" peerDependenciesMeta: prisma: optional: true - checksum: 8017b721a231ab7b2b0f932507b02bf075aae2c6f6e630a69d7089ff33bab44fa50b50dd8a81655b7202092ffc19717d484ae5f183fc4f2a1822b0d228991a7c + checksum: 38e1356644a764946c69c8691ea4bbed0ba37739d833a435625bd5435912bed4b9bdd7c384125f3a4ab8128faf566027985c0f0840a42741c338d72e40b5d565 languageName: node linkType: hard @@ -8219,6 +8304,13 @@ __metadata: languageName: node linkType: hard +"@prisma/engines-version@npm:4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81": + version: 4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81 + resolution: "@prisma/engines-version@npm:4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" + checksum: b42c6abe7c1928e546f15449e40ffa455701ef2ab1f62973628ecb4e19ff3652e34609a0d83196d1cbd0864adb44c55e082beec852b11929acf1c15fb57ca45a + languageName: node + linkType: hard + "@prisma/engines-version@npm:5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f": version: 5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f resolution: "@prisma/engines-version@npm:5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f" @@ -8226,10 +8318,10 @@ __metadata: languageName: node linkType: hard -"@prisma/engines-version@npm:5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59": - version: 5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59 - resolution: "@prisma/engines-version@npm:5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59" - checksum: c1adf540c9330a54a000c3005a4621c5d8355f2e3b159121587d33f82bf5992f567ac453f0ce76dd48fac427ec4dbc55942228fcee10d522b0d2c03bddbe422a +"@prisma/engines@npm:4.16.2": + version: 4.16.2 + resolution: "@prisma/engines@npm:4.16.2" + checksum: f423e6092c3e558cd089a68ae87459fba7fd390c433df087342b3269c3b04163965b50845150dfe47d01f811781bfff89d5ae81c95ca603c59359ab69ebd810f languageName: node linkType: hard @@ -8247,13 +8339,6 @@ __metadata: languageName: node linkType: hard -"@prisma/engines@npm:5.3.1": - version: 5.3.1 - resolution: "@prisma/engines@npm:5.3.1" - checksum: a231adad60ac42569b560ea9782bc181818d8ad15e65283d1317bda5d7aa754e5b536a3f9365ce1eda8961e1eff4eca5978c456fa9764a867fe4339d123e7371 - languageName: node - linkType: hard - "@prisma/extension-accelerate@npm:^0.6.2": version: 0.6.2 resolution: "@prisma/extension-accelerate@npm:0.6.2" @@ -8952,6 +9037,34 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-hover-card@npm:^1.0.7": + version: 1.0.7 + resolution: "@radix-ui/react-hover-card@npm:1.0.7" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-dismissable-layer": 1.0.5 + "@radix-ui/react-popper": 1.1.3 + "@radix-ui/react-portal": 1.0.4 + "@radix-ui/react-presence": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-controllable-state": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 812c348d8331348774b0460cd9058fdb34e0a4e167cc3ab7350d60d0ac374c673e8159573919da299f58860b8eeb9d43c21ccb679cf6db70f5db0386359871ef + languageName: node + linkType: hard + "@radix-ui/react-id@npm:0.1.5": version: 0.1.5 resolution: "@radix-ui/react-id@npm:0.1.5" @@ -9140,6 +9253,35 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-popper@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/react-popper@npm:1.1.3" + dependencies: + "@babel/runtime": ^7.13.10 + "@floating-ui/react-dom": ^2.0.0 + "@radix-ui/react-arrow": 1.0.3 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-callback-ref": 1.0.1 + "@radix-ui/react-use-layout-effect": 1.0.1 + "@radix-ui/react-use-rect": 1.0.1 + "@radix-ui/react-use-size": 1.0.1 + "@radix-ui/rect": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: b18a15958623f9222b6ed3e24b9fbcc2ba67b8df5a5272412f261de1592b3f05002af1c8b94c065830c3c74267ce00cf6c1d70d4d507ec92ba639501f98aa348 + languageName: node + linkType: hard + "@radix-ui/react-portal@npm:0.1.4": version: 0.1.4 resolution: "@radix-ui/react-portal@npm:0.1.4" @@ -9187,6 +9329,26 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-portal@npm:1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-portal@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/react-primitive": 1.0.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: c4cf35e2f26a89703189d0eef3ceeeb706ae0832e98e558730a5e929ca7c72c7cb510413a24eca94c7732f8d659a1e81942bec7b90540cb73ce9e4885d040b64 + languageName: node + linkType: hard + "@radix-ui/react-presence@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-presence@npm:1.0.0" @@ -12049,6 +12211,15 @@ __metadata: languageName: node linkType: hard +"@swc/helpers@npm:0.5.2": + version: 0.5.2 + resolution: "@swc/helpers@npm:0.5.2" + dependencies: + tslib: ^2.4.0 + checksum: 51d7e3d8bd56818c49d6bfbd715f0dbeedc13cf723af41166e45c03e37f109336bbcb57a1f2020f4015957721aeb21e1a7fff281233d797ff7d3dd1f447fa258 + languageName: node + linkType: hard + "@szmarczak/http-timer@npm:^4.0.5": version: 4.0.6 resolution: "@szmarczak/http-timer@npm:4.0.6" @@ -23897,6 +24068,13 @@ __metadata: languageName: node linkType: hard +"i18next-fs-backend@npm:^1.1.4": + version: 1.2.0 + resolution: "i18next-fs-backend@npm:1.2.0" + checksum: da74d20f2b007f8e34eaf442fa91ad12aaff3b9891e066c6addd6d111b37e370c62370dfbc656730ab2f8afd988f2e7ea1c48301ebb19ccb716fb5965600eddf + languageName: node + linkType: hard + "i18next-fs-backend@npm:^2.1.1": version: 2.1.3 resolution: "i18next-fs-backend@npm:2.1.3" @@ -23904,6 +24082,15 @@ __metadata: languageName: node linkType: hard +"i18next@npm:^21.8.13": + version: 21.10.0 + resolution: "i18next@npm:21.10.0" + dependencies: + "@babel/runtime": ^7.17.2 + checksum: f997985e2d4d15a62a0936a82ff6420b97f3f971e776fe685bdd50b4de0cb4dc2198bc75efe6b152844794ebd5040d8060d6d152506a687affad534834836d81 + languageName: node + linkType: hard + "i18next@npm:^23.2.3": version: 23.2.3 resolution: "i18next@npm:23.2.3" @@ -24576,7 +24763,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.11.0": +"is-core-module@npm:^2.11.0, is-core-module@npm:^2.13.0": version: 2.13.0 resolution: "is-core-module@npm:2.13.0" dependencies: @@ -26711,6 +26898,13 @@ __metadata: languageName: node linkType: hard +"lilconfig@npm:^2.1.0": + version: 2.1.0 + resolution: "lilconfig@npm:2.1.0" + checksum: 8549bb352b8192375fed4a74694cd61ad293904eee33f9d4866c2192865c44c4eb35d10782966242634e0cbc1e91fe62b1247f148dc5514918e3a966da7ea117 + languageName: node + linkType: hard + "limiter@npm:^1.1.5": version: 1.1.5 resolution: "limiter@npm:1.1.5" @@ -29155,6 +29349,31 @@ __metadata: languageName: node linkType: hard +"next-auth@npm:^4.20.1": + version: 4.23.2 + resolution: "next-auth@npm:4.23.2" + dependencies: + "@babel/runtime": ^7.20.13 + "@panva/hkdf": ^1.0.2 + cookie: ^0.5.0 + jose: ^4.11.4 + oauth: ^0.9.15 + openid-client: ^5.4.0 + preact: ^10.6.3 + preact-render-to-string: ^5.1.19 + uuid: ^8.3.2 + peerDependencies: + next: ^12.2.5 || ^13 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + nodemailer: + optional: true + checksum: 4820fdc8d9f066afd2dfe64012d7aba727fd7b82fec3a94e85ea5c1651cb4bf532d8742bfd253d9910055833f00c1c8f8f17212661f7648ecff4dd1f3e002e80 + languageName: node + linkType: hard + "next-auth@npm:^4.22.1": version: 4.22.1 resolution: "next-auth@npm:4.22.1" @@ -29204,6 +29423,24 @@ __metadata: languageName: node linkType: hard +"next-i18next@npm:^11.3.0": + version: 11.3.0 + resolution: "next-i18next@npm:11.3.0" + dependencies: + "@babel/runtime": ^7.18.6 + "@types/hoist-non-react-statics": ^3.3.1 + core-js: ^3 + hoist-non-react-statics: ^3.3.2 + i18next: ^21.8.13 + i18next-fs-backend: ^1.1.4 + react-i18next: ^11.18.0 + peerDependencies: + next: ">= 10.0.0" + react: ">= 16.8.0" + checksum: fbce97a4fbf9ad846c08652471a833c7f173c3e7ddc7cafa1423625b4a684715bb85f76ae06fe9cbed3e70f12b8e78e2459e5bc1a3c3f5c517743f17648f8939 + languageName: node + linkType: hard + "next-i18next@npm:^13.2.2": version: 13.3.0 resolution: "next-i18next@npm:13.3.0" @@ -29285,6 +29522,61 @@ __metadata: languageName: node linkType: hard +"next@npm:^13.2.1": + version: 13.5.4 + resolution: "next@npm:13.5.4" + dependencies: + "@next/env": 13.5.4 + "@next/swc-darwin-arm64": 13.5.4 + "@next/swc-darwin-x64": 13.5.4 + "@next/swc-linux-arm64-gnu": 13.5.4 + "@next/swc-linux-arm64-musl": 13.5.4 + "@next/swc-linux-x64-gnu": 13.5.4 + "@next/swc-linux-x64-musl": 13.5.4 + "@next/swc-win32-arm64-msvc": 13.5.4 + "@next/swc-win32-ia32-msvc": 13.5.4 + "@next/swc-win32-x64-msvc": 13.5.4 + "@swc/helpers": 0.5.2 + busboy: 1.6.0 + caniuse-lite: ^1.0.30001406 + postcss: 8.4.31 + styled-jsx: 5.1.1 + watchpack: 2.4.0 + peerDependencies: + "@opentelemetry/api": ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-ia32-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: f8e964ee9bbabd0303f9d807c9193833fcc47960be029c3721db9a5a35cc4ff690313e30fc6ee497f959a9141048957dddf6eb038b4a23c78c8762b0cd9d0ae0 + languageName: node + linkType: hard + "next@npm:^13.4.6": version: 13.4.6 resolution: "next@npm:13.4.6" @@ -31318,6 +31610,19 @@ __metadata: languageName: node linkType: hard +"postcss-import@npm:^15.1.0": + version: 15.1.0 + resolution: "postcss-import@npm:15.1.0" + dependencies: + postcss-value-parser: ^4.0.0 + read-cache: ^1.0.0 + resolve: ^1.1.7 + peerDependencies: + postcss: ^8.0.0 + checksum: 7bd04bd8f0235429009d0022cbf00faebc885de1d017f6d12ccb1b021265882efc9302006ba700af6cab24c46bfa2f3bc590be3f9aee89d064944f171b04e2a3 + languageName: node + linkType: hard + "postcss-js@npm:^4.0.0": version: 4.0.0 resolution: "postcss-js@npm:4.0.0" @@ -31329,6 +31634,17 @@ __metadata: languageName: node linkType: hard +"postcss-js@npm:^4.0.1": + version: 4.0.1 + resolution: "postcss-js@npm:4.0.1" + dependencies: + camelcase-css: ^2.0.1 + peerDependencies: + postcss: ^8.4.21 + checksum: 5c1e83efeabeb5a42676193f4357aa9c88f4dc1b3c4a0332c132fe88932b33ea58848186db117cf473049fc233a980356f67db490bd0a7832ccba9d0b3fd3491 + languageName: node + linkType: hard + "postcss-load-config@npm:^3.1.4": version: 3.1.4 resolution: "postcss-load-config@npm:3.1.4" @@ -31347,6 +31663,24 @@ __metadata: languageName: node linkType: hard +"postcss-load-config@npm:^4.0.1": + version: 4.0.1 + resolution: "postcss-load-config@npm:4.0.1" + dependencies: + lilconfig: ^2.0.5 + yaml: ^2.1.1 + peerDependencies: + postcss: ">=8.0.9" + ts-node: ">=9.0.0" + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + checksum: b61f890499ed7dcda1e36c20a9582b17d745bad5e2b2c7bc96942465e406bc43ae03f270c08e60d1e29dab1ee50cb26970b5eb20c9aae30e066e20bd607ae4e4 + languageName: node + linkType: hard + "postcss-loader@npm:^4.2.0": version: 4.3.0 resolution: "postcss-loader@npm:4.3.0" @@ -31473,6 +31807,17 @@ __metadata: languageName: node linkType: hard +"postcss-nested@npm:^6.0.1": + version: 6.0.1 + resolution: "postcss-nested@npm:6.0.1" + dependencies: + postcss-selector-parser: ^6.0.11 + peerDependencies: + postcss: ^8.2.14 + checksum: 7ddb0364cd797de01e38f644879189e0caeb7ea3f78628c933d91cc24f327c56d31269384454fc02ecaf503b44bfa8e08870a7c4cc56b23bc15640e1894523fa + languageName: node + linkType: hard + "postcss-pseudo-companion-classes@npm:^0.1.1": version: 0.1.1 resolution: "postcss-pseudo-companion-classes@npm:0.1.1" @@ -31527,6 +31872,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:8.4.31": + version: 8.4.31 + resolution: "postcss@npm:8.4.31" + dependencies: + nanoid: ^3.3.6 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 1d8611341b073143ad90486fcdfeab49edd243377b1f51834dc4f6d028e82ce5190e4f11bb2633276864503654fb7cab28e67abdc0fbf9d1f88cad4a0ff0beea + languageName: node + linkType: hard + "postcss@npm:^7.0.14, postcss@npm:^7.0.26, postcss@npm:^7.0.32, postcss@npm:^7.0.36, postcss@npm:^7.0.5, postcss@npm:^7.0.6": version: 7.0.39 resolution: "postcss@npm:7.0.39" @@ -31856,14 +32212,15 @@ __metadata: languageName: node linkType: hard -"prisma@npm:^5.0.0": - version: 5.3.1 - resolution: "prisma@npm:5.3.1" +"prisma@npm:^4.13.0": + version: 4.16.2 + resolution: "prisma@npm:4.16.2" dependencies: - "@prisma/engines": 5.3.1 + "@prisma/engines": 4.16.2 bin: prisma: build/index.js - checksum: e825adbcb4eec81de276de5507fb7e5486db7788c8c9de36ba6ed73f9e87d9f56b64d0e183a31dc6b80f6737ae1fbcdb110aac44ab89299af646aeb966655bef + prisma2: build/index.js + checksum: 1d0ed616abd7f8de22441e333b976705f1cb05abcb206965df3fc6a7ea03911ef467dd484a4bc51fdc6cff72dd9857b9852be5f232967a444af0a98c49bfdb76 languageName: node linkType: hard @@ -32764,6 +33121,24 @@ __metadata: languageName: node linkType: hard +"react-i18next@npm:^11.18.0": + version: 11.18.6 + resolution: "react-i18next@npm:11.18.6" + dependencies: + "@babel/runtime": ^7.14.5 + html-parse-stringify: ^3.0.1 + peerDependencies: + i18next: ">= 19.0.0" + react: ">= 16.8.0" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 624c0a0313fac4e0d18560b83c99a8bd0a83abc02e5db8d01984e0643ac409d178668aa3a4720d01f7a0d9520d38598dcbff801d6f69a970bae67461de6cd852 + languageName: node + linkType: hard + "react-i18next@npm:^12.2.0": version: 12.3.1 resolution: "react-i18next@npm:12.3.1" @@ -32887,7 +33262,7 @@ __metadata: languageName: node linkType: hard -"react-live-chat-loader@npm:^2.8.1": +"react-live-chat-loader@npm:^2.7.3, react-live-chat-loader@npm:^2.8.1": version: 2.8.1 resolution: "react-live-chat-loader@npm:2.8.1" peerDependencies: @@ -34139,6 +34514,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.2": + version: 1.22.6 + resolution: "resolve@npm:1.22.6" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: d13bf66d4e2ee30d291491f16f2fa44edd4e0cefb85d53249dd6f93e70b2b8c20ec62f01b18662e3cd40e50a7528f18c4087a99490048992a3bb954cf3201a5b + 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" @@ -34188,6 +34576,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@^1.22.2#~builtin": + version: 1.22.6 + resolution: "resolve@patch:resolve@npm%3A1.22.6#~builtin::version=1.22.6&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: 9d3b3c67aefd12cecbe5f10ca4d1f51ea190891096497c43f301b086883b426466918c3a64f1bbf1788fabb52b579d58809614006c5d0b49186702b3b8fb746a + 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" @@ -36319,6 +36720,24 @@ __metadata: languageName: node linkType: hard +"sucrase@npm:^3.32.0": + version: 3.34.0 + resolution: "sucrase@npm:3.34.0" + dependencies: + "@jridgewell/gen-mapping": ^0.3.2 + commander: ^4.0.0 + glob: 7.1.6 + lines-and-columns: ^1.1.6 + mz: ^2.7.0 + pirates: ^4.0.1 + ts-interface-checker: ^0.1.9 + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 61860063bdf6103413698e13247a3074d25843e91170825a9752e4af7668ffadd331b6e99e92fc32ee5b3c484ee134936f926fa9039d5711fafff29d017a2110 + languageName: node + linkType: hard + "superagent@npm:^5.1.1": version: 5.3.1 resolution: "superagent@npm:5.3.1" @@ -36680,6 +37099,39 @@ __metadata: languageName: node linkType: hard +"tailwindcss@npm:^3.2.1": + version: 3.3.3 + resolution: "tailwindcss@npm:3.3.3" + dependencies: + "@alloc/quick-lru": ^5.2.0 + arg: ^5.0.2 + chokidar: ^3.5.3 + didyoumean: ^1.2.2 + dlv: ^1.1.3 + fast-glob: ^3.2.12 + glob-parent: ^6.0.2 + is-glob: ^4.0.3 + jiti: ^1.18.2 + lilconfig: ^2.1.0 + micromatch: ^4.0.5 + normalize-path: ^3.0.0 + object-hash: ^3.0.0 + picocolors: ^1.0.0 + postcss: ^8.4.23 + postcss-import: ^15.1.0 + postcss-js: ^4.0.1 + postcss-load-config: ^4.0.1 + postcss-nested: ^6.0.1 + postcss-selector-parser: ^6.0.11 + resolve: ^1.22.2 + sucrase: ^3.32.0 + bin: + tailwind: lib/cli.js + tailwindcss: lib/cli.js + checksum: 0195c7a3ebb0de5e391d2a883d777c78a4749f0c532d204ee8aea9129f2ed8e701d8c0c276aa5f7338d07176a3c2a7682c1d0ab9c8a6c2abe6d9325c2954eb50 + languageName: node + linkType: hard + "tailwindcss@npm:^3.3.1": version: 3.3.1 resolution: "tailwindcss@npm:3.3.1" @@ -40575,6 +41027,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.1.1, yaml@npm:^2.3.1": + version: 2.3.2 + resolution: "yaml@npm:2.3.2" + checksum: acd80cc24df12c808c6dec8a0176d404ef9e6f08ad8786f746ecc9d8974968c53c6e8a67fdfabcc5f99f3dc59b6bb0994b95646ff03d18e9b1dcd59eccc02146 + languageName: node + linkType: hard + "yaml@npm:^2.2.1": version: 2.3.1 resolution: "yaml@npm:2.3.1" @@ -40582,13 +41041,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.3.1": - version: 2.3.2 - resolution: "yaml@npm:2.3.2" - checksum: acd80cc24df12c808c6dec8a0176d404ef9e6f08ad8786f746ecc9d8974968c53c6e8a67fdfabcc5f99f3dc59b6bb0994b95646ff03d18e9b1dcd59eccc02146 - languageName: node - linkType: hard - "yargs-parser@npm:^18.1.2, yargs-parser@npm:^18.1.3": version: 18.1.3 resolution: "yargs-parser@npm:18.1.3" @@ -40822,6 +41274,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.20.2": + version: 3.22.4 + resolution: "zod@npm:3.22.4" + checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f + languageName: node + linkType: hard + "zod@npm:^3.21.4, zod@npm:^3.22.2": version: 3.22.2 resolution: "zod@npm:3.22.2"