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 ? (
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.parentId && (
+
+ )}
{team.name}
{!team.accepted && (
@@ -517,11 +519,13 @@ const SettingsSidebarContainer = ({
)}
-
+ {!otherTeam.parentId && (
+
+ )}
{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"