diff --git a/.env.example b/.env.example
index 4ddb81f5eb..0a75c7390a 100644
--- a/.env.example
+++ b/.env.example
@@ -178,4 +178,4 @@ CSP_POLICY=
# Vercel Edge Config
EDGE_CONFIG=
-NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
\ No newline at end of file
+NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
diff --git a/apps/storybook/.storybook/main.js b/apps/storybook/.storybook/main.js
index 3b7382f1ed..9b0d21eada 100644
--- a/apps/storybook/.storybook/main.js
+++ b/apps/storybook/.storybook/main.js
@@ -4,6 +4,7 @@ module.exports = {
stories: [
"../intro.stories.mdx",
"../../../packages/ui/components/**/*.stories.mdx",
+ "../../../packages/atoms/**/*.stories.mdx",
"../../../packages/features/**/*.stories.mdx",
"../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)",
],
@@ -70,4 +71,5 @@ module.exports = {
return config;
},
+ typescript: { reactDocgen: 'react-docgen' }
};
diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx
index ff2dd7ca07..42e9bb454d 100644
--- a/apps/web/components/booking/pages/BookingPage.tsx
+++ b/apps/web/components/booking/pages/BookingPage.tsx
@@ -21,6 +21,7 @@ import {
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
+import { createBooking, createRecurringBooking } from "@calcom/features/bookings/lib";
import {
getBookingFieldsWithSystemFields,
SystemField,
@@ -38,6 +39,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
+import { parseDate, parseRecurringDates } from "@calcom/lib/parse-dates";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { TimeFormat } from "@calcom/lib/timeFormat";
@@ -47,9 +49,6 @@ import { AlertTriangle, Calendar, RefreshCw, User } from "@calcom/ui/components/
import { timeZone } from "@lib/clock";
import useRouterQuery from "@lib/hooks/useRouterQuery";
-import createBooking from "@lib/mutations/bookings/create-booking";
-import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
-import { parseRecurringDates, parseDate } from "@lib/parseDate";
import type { Gate, GateState } from "@components/Gates";
import Gates from "@components/Gates";
@@ -433,7 +432,6 @@ const BookingPage = ({
// Calculate the booking date(s)
let recurringStrings: string[] = [],
recurringDates: Date[] = [];
-
if (eventType.recurringEvent?.freq && recurringEventCount !== null) {
[recurringStrings, recurringDates] = parseRecurringDates(
{
@@ -443,7 +441,7 @@ const BookingPage = ({
recurringCount: parseInt(recurringEventCount.toString()),
selectedTimeFormat: timeFormat,
},
- i18n
+ i18n.language
);
}
@@ -572,7 +570,7 @@ const BookingPage = ({
{isClientTimezoneAvailable &&
(rescheduleUid || !eventType.recurringEvent?.freq) &&
- `${parseDate(date, i18n, timeFormat)}`}
+ `${parseDate(date, i18n.language, { selectedTimeFormat: timeFormat })}`}
{isClientTimezoneAvailable &&
!rescheduleUid &&
eventType.recurringEvent?.freq &&
@@ -602,7 +600,9 @@ const BookingPage = ({
{isClientTimezoneAvailable &&
typeof booking.startTime === "string" &&
- parseDate(dayjs(booking.startTime), i18n, timeFormat)}
+ parseDate(dayjs(booking.startTime), i18n.language, {
+ selectedTimeFormat: timeFormat,
+ })}
)}
diff --git a/apps/web/components/error/error-page.tsx b/apps/web/components/error/error-page.tsx
index b394191129..e3102f230b 100644
--- a/apps/web/components/error/error-page.tsx
+++ b/apps/web/components/error/error-page.tsx
@@ -1,6 +1,6 @@
import React from "react";
-import { HttpError } from "@lib/core/http/error";
+import { HttpError } from "@calcom/lib/http-error";
type Props = {
statusCode?: number | null;
diff --git a/apps/web/lib/mutations/bookings/create-booking.ts b/apps/web/lib/mutations/bookings/create-booking.ts
deleted file mode 100644
index d0b677adab..0000000000
--- a/apps/web/lib/mutations/bookings/create-booking.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
-
-import * as fetch from "@lib/core/http/fetch-wrapper";
-import type { BookingResponse } from "@lib/types/booking";
-
-type BookingCreateBodyForMutation = Omit;
-const createBooking = async (data: BookingCreateBodyForMutation) => {
- const response = await fetch.post("/api/book/event", data);
-
- return response;
-};
-
-export default createBooking;
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index e3108db4c3..f859499763 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -1,6 +1,7 @@
require("dotenv").config({ path: "../../.env" });
const CopyWebpackPlugin = require("copy-webpack-plugin");
const os = require("os");
+const glob = require("glob");
const { withAxiom } = require("next-axiom");
const { i18n } = require("./next-i18next.config");
@@ -66,6 +67,18 @@ if (process.env.ANALYZE === "true") {
}
plugins.push(withAxiom);
+
+/** Needed to rewrite public booking page, gets all static pages but [user] */
+const pages = glob
+ .sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
+ .map((filename) =>
+ filename
+ .substr(6)
+ .replace(/(\.tsx|\.js|\.ts)/, "")
+ .replace(/\/.*/, "")
+ )
+ .filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
+
/** @type {import("next").NextConfig} */
const nextConfig = {
i18n,
@@ -198,6 +211,16 @@ const nextConfig = {
source: "/embed/embed.js",
destination: process.env.NEXT_PUBLIC_EMBED_LIB_URL?,
}, */
+ {
+ source: `/:user((?!${pages.join("|")}).*)/:type`,
+ destination: "/new-booker/:user/:type",
+ has: [{ type: "cookie", key: "new-booker-enabled" }],
+ },
+ {
+ source: "/team/:slug/:type",
+ destination: "/new-booker/team/:slug/:type",
+ has: [{ type: "cookie", key: "new-booker-enabled" }],
+ },
];
},
async headers() {
diff --git a/apps/web/pages/[user]/book.tsx b/apps/web/pages/[user]/book.tsx
index 1a914ef05d..741f35882d 100644
--- a/apps/web/pages/[user]/book.tsx
+++ b/apps/web/pages/[user]/book.tsx
@@ -5,6 +5,8 @@ import type { LocationObject } from "@calcom/app-store/locations";
import { privacyFilteredLocations } from "@calcom/app-store/locations";
import { getAppFromSlug } from "@calcom/app-store/utils";
import dayjs from "@calcom/dayjs";
+import getBooking from "@calcom/features/bookings/lib/get-booking";
+import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
import { parseRecurringEvent } from "@calcom/lib";
import {
@@ -13,8 +15,6 @@ import {
getGroupName,
getUsernameList,
} from "@calcom/lib/defaultEvents";
-import getBooking from "@calcom/lib/getBooking";
-import type { GetBookingType } from "@calcom/lib/getBooking";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import prisma, { bookEventTypeSelect } from "@calcom/prisma";
diff --git a/apps/web/pages/_error.tsx b/apps/web/pages/_error.tsx
index 59869c4697..fb0ce29505 100644
--- a/apps/web/pages/_error.tsx
+++ b/apps/web/pages/_error.tsx
@@ -8,10 +8,9 @@ import NextError from "next/error";
import React from "react";
import { getErrorFromUnknown } from "@calcom/lib/errors";
+import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
-import { HttpError } from "@lib/core/http/error";
-
import { ErrorPage } from "@components/error/error-page";
// Adds HttpException to the list of possible error types.
diff --git a/apps/web/pages/api/auth/oidc.ts b/apps/web/pages/api/auth/oidc.ts
index f8cd8799eb..0e65b2b551 100644
--- a/apps/web/pages/api/auth/oidc.ts
+++ b/apps/web/pages/api/auth/oidc.ts
@@ -1,8 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next";
import jackson from "@calcom/features/ee/sso/lib/jackson";
-
-import { HttpError } from "@lib/core/http/error";
+import { HttpError } from "@calcom/lib/http-error";
// This is the callback endpoint for the OIDC provider
// A team must set this endpoint in the OIDC provider's configuration
diff --git a/apps/web/pages/api/auth/saml/authorize.ts b/apps/web/pages/api/auth/saml/authorize.ts
index 8d42bec42e..337406cb4f 100644
--- a/apps/web/pages/api/auth/saml/authorize.ts
+++ b/apps/web/pages/api/auth/saml/authorize.ts
@@ -2,8 +2,7 @@ import type { OAuthReq } from "@boxyhq/saml-jackson";
import type { NextApiRequest, NextApiResponse } from "next";
import jackson from "@calcom/features/ee/sso/lib/jackson";
-
-import type { HttpError } from "@lib/core/http/error";
+import type { HttpError } from "@calcom/lib/http-error";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { oauthController } = await jackson();
diff --git a/apps/web/pages/api/integrations/[...args].ts b/apps/web/pages/api/integrations/[...args].ts
index 228b896125..5b789e8ffd 100644
--- a/apps/web/pages/api/integrations/[...args].ts
+++ b/apps/web/pages/api/integrations/[...args].ts
@@ -4,12 +4,11 @@ import type { Session } from "next-auth";
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
+import { HttpError } from "@calcom/lib/http-error";
import { revalidateCalendarCache } from "@calcom/lib/server/revalidateCalendarCache";
import prisma from "@calcom/prisma";
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
-import { HttpError } from "@lib/core/http/error";
-
const defaultIntegrationAddHandler = async ({
slug,
supportsMultipleInstalls,
diff --git a/apps/web/pages/api/newbooker/[status].tsx b/apps/web/pages/api/newbooker/[status].tsx
new file mode 100644
index 0000000000..13bc1db346
--- /dev/null
+++ b/apps/web/pages/api/newbooker/[status].tsx
@@ -0,0 +1,26 @@
+import type { NextApiRequest, NextApiResponse } from "next";
+import { z } from "zod";
+
+import { defaultResponder } from "@calcom/lib/server";
+
+const newBookerSchema = z.object({
+ status: z.enum(["enable", "disable"]),
+});
+
+/**
+ * Very basic temporary api route to enable/disable new booker access.
+ */
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ const { status } = newBookerSchema.parse(req.query);
+
+ if (status === "enable") {
+ const expires = new Date();
+ expires.setFullYear(expires.getFullYear() + 1);
+ res.setHeader("Set-Cookie", `new-booker-enabled=true; path=/; expires=${expires.toUTCString()}`);
+ } else {
+ res.setHeader("Set-Cookie", "new-booker-enabled=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT");
+ }
+ res.send({ status: 200, body: `Done – ${status}` });
+}
+
+export default defaultResponder(handler);
diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx
index b019cb9594..aa25e7e84b 100644
--- a/apps/web/pages/availability/[schedule].tsx
+++ b/apps/web/pages/availability/[schedule].tsx
@@ -10,6 +10,7 @@ import { availabilityAsString } from "@calcom/lib/availability";
import { yyyymmdd } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
+import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import type { Schedule as ScheduleType, TimeRange, WorkingHours } from "@calcom/types/schedule";
@@ -35,8 +36,6 @@ import {
} from "@calcom/ui";
import { Info, Plus, Trash, MoreHorizontal } from "@calcom/ui/components/icon";
-import { HttpError } from "@lib/core/http/error";
-
import PageWrapper from "@components/PageWrapper";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
import EditableHeading from "@components/ui/EditableHeading";
diff --git a/apps/web/pages/availability/index.tsx b/apps/web/pages/availability/index.tsx
index 093992eaf7..8c0cb069d7 100644
--- a/apps/web/pages/availability/index.tsx
+++ b/apps/web/pages/availability/index.tsx
@@ -3,13 +3,13 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { HttpError } from "@calcom/lib/http-error";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { EmptyScreen, showToast } from "@calcom/ui";
import { Clock } from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
-import { HttpError } from "@lib/core/http/error";
import PageWrapper from "@components/PageWrapper";
import SkeletonLoader from "@components/availability/SkeletonLoader";
diff --git a/apps/web/pages/booking/[uid].tsx b/apps/web/pages/booking/[uid].tsx
index ca02ce8376..20a5bd6141 100644
--- a/apps/web/pages/booking/[uid].tsx
+++ b/apps/web/pages/booking/[uid].tsx
@@ -24,6 +24,7 @@ import {
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
+import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking";
import {
SystemField,
getBookingFieldsWithSystemFields,
@@ -36,7 +37,6 @@ import {
formatToLocalizedTimezone,
} from "@calcom/lib/date-fns";
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
-import { getBookingWithResponses } from "@calcom/lib/getBooking";
import useGetBrandingColours from "@calcom/lib/getBrandColours";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
diff --git a/apps/web/pages/d/[link]/[slug].tsx b/apps/web/pages/d/[link]/[slug].tsx
index 4ab511ec1b..3017918cdb 100644
--- a/apps/web/pages/d/[link]/[slug].tsx
+++ b/apps/web/pages/d/[link]/[slug].tsx
@@ -3,9 +3,9 @@ import { z } from "zod";
import type { LocationObject } from "@calcom/core/location";
import { privacyFilteredLocations } from "@calcom/core/location";
+import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { parseRecurringEvent } from "@calcom/lib";
import { getWorkingHours } from "@calcom/lib/availability";
-import type { GetBookingType } from "@calcom/lib/getBooking";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { availiblityPageEventTypeSelect } from "@calcom/prisma";
import prisma from "@calcom/prisma";
diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx
index e2ec5c7043..0f679ea447 100644
--- a/apps/web/pages/event-types/index.tsx
+++ b/apps/web/pages/event-types/index.tsx
@@ -17,6 +17,7 @@ import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
+import { HttpError } from "@calcom/lib/http-error";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import {
@@ -59,7 +60,6 @@ import {
} from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
-import { HttpError } from "@lib/core/http/error";
import { EmbedButton, EmbedDialog } from "@components/Embed";
import PageWrapper from "@components/PageWrapper";
diff --git a/apps/web/pages/new-booker/[user]/[type].tsx b/apps/web/pages/new-booker/[user]/[type].tsx
new file mode 100644
index 0000000000..6a6db3e0e8
--- /dev/null
+++ b/apps/web/pages/new-booker/[user]/[type].tsx
@@ -0,0 +1,112 @@
+import type { GetServerSidePropsContext } from "next";
+import { z } from "zod";
+
+import { Booker } from "@calcom/atoms";
+import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
+import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
+import { getUsernameList } from "@calcom/lib/defaultEvents";
+import prisma from "@calcom/prisma";
+
+import type { inferSSRProps } from "@lib/types/inferSSRProps";
+
+import PageWrapper from "@components/PageWrapper";
+
+type PageProps = inferSSRProps;
+
+export default function Type({ slug, user, booking, away }: PageProps) {
+ return (
+
+
+
+ );
+}
+
+Type.PageWrapper = PageWrapper;
+
+async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
+ const { user, type: slug } = paramsSchema.parse(context.params);
+ const { rescheduleUid } = context.query;
+
+ const { ssgInit } = await import("@server/lib/ssg");
+ const ssg = await ssgInit(context);
+ const usernameList = getUsernameList(user);
+
+ const users = await prisma.user.findMany({
+ where: {
+ username: {
+ in: usernameList,
+ },
+ },
+ select: {
+ allowDynamicBooking: true,
+ },
+ });
+
+ if (!users.length) {
+ return {
+ notFound: true,
+ };
+ }
+
+ let booking: GetBookingType | null = null;
+ if (rescheduleUid) {
+ booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
+ }
+
+ return {
+ props: {
+ booking,
+ user,
+ slug,
+ away: false,
+ trpcState: ssg.dehydrate(),
+ },
+ };
+}
+
+async function getUserPageProps(context: GetServerSidePropsContext) {
+ const { user: username, type: slug } = paramsSchema.parse(context.params);
+ const { rescheduleUid } = context.query;
+ const { ssgInit } = await import("@server/lib/ssg");
+ const ssg = await ssgInit(context);
+ const user = await prisma.user.findUnique({
+ where: {
+ username,
+ },
+ select: {
+ away: true,
+ },
+ });
+
+ if (!user) {
+ return {
+ notFound: true,
+ };
+ }
+
+ let booking: GetBookingType | null = null;
+ if (rescheduleUid) {
+ booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
+ }
+
+ return {
+ props: {
+ booking,
+ away: user?.away,
+ user: username,
+ slug,
+ trpcState: ssg.dehydrate(),
+ },
+ };
+}
+
+const paramsSchema = z.object({ type: z.string(), user: z.string() });
+
+// Booker page fetches a tiny bit of data server side, to determine early
+// whether the page should show an away state or dynamic booking not allowed.
+export const getServerSideProps = async (context: GetServerSidePropsContext) => {
+ const { user } = paramsSchema.parse(context.params);
+ const isDynamicGroup = user.includes("+");
+
+ return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context);
+};
diff --git a/apps/web/pages/new-booker/team/[slug]/[type].tsx b/apps/web/pages/new-booker/team/[slug]/[type].tsx
new file mode 100644
index 0000000000..46a91e6890
--- /dev/null
+++ b/apps/web/pages/new-booker/team/[slug]/[type].tsx
@@ -0,0 +1,65 @@
+import type { GetServerSidePropsContext } from "next";
+import { z } from "zod";
+
+import { Booker } from "@calcom/atoms";
+import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
+import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
+import prisma from "@calcom/prisma";
+
+import type { inferSSRProps } from "@lib/types/inferSSRProps";
+
+import PageWrapper from "@components/PageWrapper";
+
+type PageProps = inferSSRProps;
+
+export default function Type({ slug, user, booking, away }: PageProps) {
+ return (
+
+
+
+ );
+}
+
+Type.PageWrapper = PageWrapper;
+
+const paramsSchema = z.object({ type: z.string(), slug: z.string() });
+
+// Booker page fetches a tiny bit of data server side:
+// 1. Check if team exists, to show 404
+// 2. If rescheduling, get the booking details
+export const getServerSideProps = async (context: GetServerSidePropsContext) => {
+ const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params);
+ const { rescheduleUid } = context.query;
+ const { ssgInit } = await import("@server/lib/ssg");
+ const ssg = await ssgInit(context);
+
+ const team = await prisma.team.findFirst({
+ where: {
+ slug: teamSlug,
+ },
+ select: {
+ id: true,
+ },
+ });
+
+ if (!team) {
+ return {
+ notFound: true,
+ };
+ }
+
+ let booking: GetBookingType | null = null;
+ if (rescheduleUid) {
+ booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
+ }
+
+ return {
+ props: {
+ booking,
+ away: false,
+ user: teamSlug,
+ slug: meetingSlug,
+ trpcState: ssg.dehydrate(),
+ },
+ };
+};
diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx
index 59da20570f..1eeb235b24 100644
--- a/apps/web/pages/team/[slug]/[type].tsx
+++ b/apps/web/pages/team/[slug]/[type].tsx
@@ -2,10 +2,10 @@ import type { GetServerSidePropsContext } from "next";
import type { LocationObject } from "@calcom/core/location";
import { privacyFilteredLocations } from "@calcom/core/location";
+import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
+import getBooking from "@calcom/features/bookings/lib/get-booking";
import { parseRecurringEvent } from "@calcom/lib";
import { getWorkingHours } from "@calcom/lib/availability";
-import getBooking from "@calcom/lib/getBooking";
-import type { GetBookingType } from "@calcom/lib/getBooking";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
diff --git a/apps/web/pages/team/[slug]/book.tsx b/apps/web/pages/team/[slug]/book.tsx
index 35d73d6553..6f85ead949 100644
--- a/apps/web/pages/team/[slug]/book.tsx
+++ b/apps/web/pages/team/[slug]/book.tsx
@@ -3,10 +3,10 @@ import { z } from "zod";
import type { LocationObject } from "@calcom/app-store/locations";
import { privacyFilteredLocations } from "@calcom/app-store/locations";
+import getBooking from "@calcom/features/bookings/lib/get-booking";
+import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
import { parseRecurringEvent } from "@calcom/lib";
-import type { GetBookingType } from "@calcom/lib/getBooking";
-import getBooking from "@calcom/lib/getBooking";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import prisma from "@calcom/prisma";
import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts
index 7a956a3871..985125be5a 100644
--- a/apps/web/playwright/booking-pages.e2e.ts
+++ b/apps/web/playwright/booking-pages.e2e.ts
@@ -1,6 +1,7 @@
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
+import { testBothBookers } from "./lib/new-booker";
import {
bookFirstEvent,
bookOptinEvent,
@@ -12,7 +13,7 @@ import {
test.describe.configure({ mode: "parallel" });
test.afterEach(async ({ users }) => users.deleteAll());
-test.describe("free user", () => {
+testBothBookers.describe("free user", (bookerVariant) => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create();
await page.goto(`/${free.username}`);
@@ -24,12 +25,18 @@ test.describe("free user", () => {
await selectFirstAvailableTimeSlotNextMonth(page);
- // Navigate to book page
- await page.waitForNavigation({
- url(url) {
- return url.pathname.endsWith("/book");
- },
- });
+ // Kept in if statement here, since it's only temporary
+ // until the old booker isn't used anymore, and I wanted
+ // to change the test as little as possible.
+ // eslint-disable-next-line playwright/no-conditional-in-test
+ if (bookerVariant !== "new-booker") {
+ // Navigate to book page
+ await page.waitForNavigation({
+ url(url) {
+ return url.pathname.endsWith("/book");
+ },
+ });
+ }
// save booking url
const bookingUrl: string = page.url();
@@ -51,7 +58,7 @@ test.describe("free user", () => {
});
});
-test.describe("pro user", () => {
+testBothBookers.describe("pro user", () => {
test.beforeEach(async ({ page, users }) => {
const pro = await users.create();
await page.goto(`/${pro.username}`);
diff --git a/apps/web/playwright/booking-seats.e2e.ts b/apps/web/playwright/booking-seats.e2e.ts
index 35619f9409..2a872151d6 100644
--- a/apps/web/playwright/booking-seats.e2e.ts
+++ b/apps/web/playwright/booking-seats.e2e.ts
@@ -7,6 +7,7 @@ import prisma from "@calcom/prisma";
import type { Fixtures } from "./lib/fixtures";
import { test } from "./lib/fixtures";
+import { testBothBookers } from "./lib/new-booker";
import {
bookTimeSlot,
createNewSeatedEventType,
@@ -46,7 +47,7 @@ async function createUserWithSeatedEventAndAttendees(
return { user, eventType, booking };
}
-test.describe("Booking with Seats", () => {
+testBothBookers.describe("Booking with Seats", (bookerVariant) => {
test("User can create a seated event (2 seats as example)", async ({ users, page }) => {
const user = await users.create({ name: "Seated event" });
await user.login();
@@ -64,11 +65,19 @@ test.describe("Booking with Seats", () => {
});
await page.goto(`/${user.username}/${slug}`);
await selectFirstAvailableTimeSlotNextMonth(page);
- await page.waitForNavigation({
- url(url) {
- return url.pathname.endsWith("/book");
- },
- });
+
+ // Kept in if statement here, since it's only temporary
+ // until the old booker isn't used anymore, and I wanted
+ // to change the test as little as possible.
+ // eslint-disable-next-line playwright/no-conditional-in-test
+ if (bookerVariant === "old-booker") {
+ await page.waitForNavigation({
+ url(url) {
+ return url.pathname.endsWith("/book");
+ },
+ });
+ }
+
const bookingUrl = page.url();
await test.step("Attendee #1 can book a seated event time slot", async () => {
await page.goto(bookingUrl);
@@ -93,7 +102,7 @@ test.describe("Booking with Seats", () => {
// TODO: Make E2E test: All attendees canceling should delete the booking for the User
// todo("All attendees canceling should delete the booking for the User");
- test.describe("Reschedule for booking with seats", () => {
+ testBothBookers.describe("Reschedule for booking with seats", () => {
test("Should reschedule booking with seats", async ({ page, users, bookings }) => {
const { booking } = await createUserWithSeatedEventAndAttendees({ users, bookings }, [
{ name: "John First", email: "first+seats@cal.com", timeZone: "Europe/Berlin" },
diff --git a/apps/web/playwright/event-types.e2e.ts b/apps/web/playwright/event-types.e2e.ts
index bdfe43ae69..d3a12f1622 100644
--- a/apps/web/playwright/event-types.e2e.ts
+++ b/apps/web/playwright/event-types.e2e.ts
@@ -4,12 +4,13 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { randomString } from "@calcom/lib/random";
import { test } from "./lib/fixtures";
+import { testBothBookers } from "./lib/new-booker";
import { bookTimeSlot, createNewEventType, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
test.describe("Event Types tests", () => {
- test.describe("user", () => {
+ testBothBookers.describe("user", (bookerVariant) => {
test.beforeEach(async ({ page, users }) => {
const user = await users.create();
await user.login();
@@ -147,11 +148,17 @@ test.describe("Event Types tests", () => {
await selectFirstAvailableTimeSlotNextMonth(page);
// Navigate to book page
- await page.waitForNavigation({
- url(url) {
- return url.pathname.endsWith("/book");
- },
- });
+ // Kept in if statement here, since it's only temporary
+ // until the old booker isn't used anymore, and I wanted
+ // to change the test as little as possible.
+ // eslint-disable-next-line playwright/no-conditional-in-test
+ if (bookerVariant === "old-booker") {
+ await page.waitForNavigation({
+ url(url) {
+ return url.pathname.endsWith("/book");
+ },
+ });
+ }
for (const location of locationData) {
await page.locator(`span:has-text("${location}")`).click();
diff --git a/apps/web/playwright/lib/fixtures.ts b/apps/web/playwright/lib/fixtures.ts
index 2868cc1c0d..7b86f34f75 100644
--- a/apps/web/playwright/lib/fixtures.ts
+++ b/apps/web/playwright/lib/fixtures.ts
@@ -1,3 +1,4 @@
+import type { Page } from "@playwright/test";
import { test as base } from "@playwright/test";
import prisma from "@calcom/prisma";
@@ -10,6 +11,7 @@ import { createServersFixture } from "../fixtures/servers";
import { createUsersFixture } from "../fixtures/users";
export interface Fixtures {
+ page: Page;
users: ReturnType;
bookings: ReturnType;
payments: ReturnType;
diff --git a/apps/web/playwright/lib/new-booker.ts b/apps/web/playwright/lib/new-booker.ts
new file mode 100644
index 0000000000..7d9fea6057
--- /dev/null
+++ b/apps/web/playwright/lib/new-booker.ts
@@ -0,0 +1,31 @@
+import { test } from "./fixtures";
+
+export type BookerVariants = "new-booker" | "old-booker";
+
+const bookerVariants = ["new-booker", "old-booker"];
+
+/**
+ * Small wrapper around test.describe().
+ * When using testbothBookers.describe() instead of test.describe(), this will run the specified
+ * tests twice. One with the old booker, and one with the new booker. It will also add the booker variant
+ * name to the test name for easier debugging.
+ * Finally it also adds a parameter bookerVariant to your testBothBooker.describe() callback, which
+ * can be used to do any conditional rendering in the test for a specific booker variant (should be as little
+ * as possible).
+ *
+ * See apps/web/playwright/booking-pages.e2e.ts for an example.
+ */
+export const testBothBookers = {
+ describe: (testName: string, testFn: (bookerVariant: BookerVariants) => void) => {
+ bookerVariants.forEach((bookerVariant) => {
+ test.describe(`${testName} -- ${bookerVariant}`, () => {
+ if (bookerVariant === "new-booker") {
+ test.beforeEach(({ context }) => {
+ context.addCookies([{ name: "new-booker-enabled", value: "true", url: "http://localhost:3000" }]);
+ });
+ }
+ testFn(bookerVariant as BookerVariants);
+ });
+ });
+ },
+};
diff --git a/apps/web/playwright/manage-booking-questions.e2e.ts b/apps/web/playwright/manage-booking-questions.e2e.ts
index cb0f30d772..7aa5706999 100644
--- a/apps/web/playwright/manage-booking-questions.e2e.ts
+++ b/apps/web/playwright/manage-booking-questions.e2e.ts
@@ -7,6 +7,8 @@ import { uuid } from "short-uuid";
import prisma from "@calcom/prisma";
import { test } from "./lib/fixtures";
+import { testBothBookers } from "./lib/new-booker";
+import type { BookerVariants } from "./lib/new-booker";
import { createHttpServer, waitFor, selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils";
async function getLabelText(field: Locator) {
@@ -18,7 +20,7 @@ test.describe("Manage Booking Questions", () => {
await users.deleteAll();
});
- test.describe("For User EventType", () => {
+ testBothBookers.describe("For User EventType", (bookerVariant) => {
test("Do a booking with a user added question and verify a few thing in b/w", async ({
page,
users,
@@ -37,11 +39,11 @@ test.describe("Manage Booking Questions", () => {
await firstEventTypeElement.click();
});
- await runTestStepsCommonForTeamAndUserEventType(page, context, webhookReceiver);
+ await runTestStepsCommonForTeamAndUserEventType(page, context, webhookReceiver, bookerVariant);
});
});
- test.describe("For Team EventType", () => {
+ testBothBookers.describe("For Team EventType", (bookerVariant) => {
test("Do a booking with a user added question and verify a few thing in b/w", async ({
page,
users,
@@ -60,7 +62,7 @@ test.describe("Manage Booking Questions", () => {
await firstEventTypeElement.click();
});
- await runTestStepsCommonForTeamAndUserEventType(page, context, webhookReceiver);
+ await runTestStepsCommonForTeamAndUserEventType(page, context, webhookReceiver, bookerVariant);
});
});
});
@@ -73,7 +75,8 @@ async function runTestStepsCommonForTeamAndUserEventType(
close: () => import("http").Server;
requestList: (import("http").IncomingMessage & { body?: unknown })[];
url: string;
- }
+ },
+ bookerVariant: BookerVariants
) {
await page.click('[href$="tabName=advanced"]');
@@ -89,7 +92,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
},
});
- await doOnFreshPreview(page, context, async (page) => {
+ await doOnFreshPreview(page, context, bookerVariant, async (page) => {
const allFieldsLocator = await expectSystemFieldsToBeThere(page);
const userFieldLocator = allFieldsLocator.nth(5);
@@ -105,7 +108,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
name: "how_are_you",
page,
});
- await doOnFreshPreview(page, context, async (page) => {
+ await doOnFreshPreview(page, context, bookerVariant, async (page) => {
const formBuilderFieldLocator = page.locator('[data-fob-field-name="how_are_you"]');
await expect(formBuilderFieldLocator).toBeHidden();
});
@@ -119,7 +122,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
});
await test.step('Try to book without providing "How are you?" response', async () => {
- await doOnFreshPreview(page, context, async (page) => {
+ await doOnFreshPreview(page, context, bookerVariant, async (page) => {
await bookTimeSlot({ page, name: "Booker", email: "booker@example.com" });
await expectErrorToBeThereFor({ page, name: "how_are_you" });
});
@@ -138,6 +141,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
return await doOnFreshPreview(
page,
context,
+ bookerVariant,
async (page) => {
const formBuilderFieldLocator = page.locator('[data-fob-field-name="how_are_you"]');
await expect(formBuilderFieldLocator).toBeVisible();
@@ -196,8 +200,7 @@ async function runTestStepsCommonForTeamAndUserEventType(
await test.step("Do a reschedule and notice that we can't book without giving a value for rescheduleReason", async () => {
const page = previewTabPage;
- await rescheduleFromTheLinkOnPage({ page });
- // await page.pause();
+ await rescheduleFromTheLinkOnPage({ page, bookerVariant });
await expectErrorToBeThereFor({ page, name: "rescheduleReason" });
});
}
@@ -304,10 +307,11 @@ async function expectErrorToBeThereFor({ page, name }: { page: Page; name: strin
async function doOnFreshPreview(
page: Page,
context: PlaywrightTestArgs["context"],
+ bookerVariant: BookerVariants,
callback: (page: Page) => Promise,
persistTab = false
) {
- const previewTabPage = await openBookingFormInPreviewTab(context, page);
+ const previewTabPage = await openBookingFormInPreviewTab(context, page, bookerVariant);
await callback(previewTabPage);
if (!persistTab) {
await previewTabPage.close();
@@ -347,25 +351,39 @@ async function createAndLoginUserWithEventTypes({ users }: { users: ReturnType url.pathname.endsWith("/book"),
- });
+ if (bookerVariant === "old-booker") {
+ await page.waitForNavigation({
+ url: (url) => url.pathname.endsWith("/book"),
+ });
+ }
await page.click('[data-testid="confirm-reschedule-button"]');
}
-async function openBookingFormInPreviewTab(context: PlaywrightTestArgs["context"], page: Page) {
+async function openBookingFormInPreviewTab(
+ context: PlaywrightTestArgs["context"],
+ page: Page,
+ bookerVariant: BookerVariants
+) {
const previewTabPromise = context.waitForEvent("page");
await page.locator('[data-testid="preview-button"]').click();
const previewTabPage = await previewTabPromise;
await previewTabPage.waitForLoadState();
await selectFirstAvailableTimeSlotNextMonth(previewTabPage);
- await previewTabPage.waitForNavigation({
- url: (url) => url.pathname.endsWith("/book"),
- });
+ if (bookerVariant === "old-booker") {
+ await previewTabPage.waitForNavigation({
+ url: (url) => url.pathname.endsWith("/book"),
+ });
+ }
return previewTabPage;
}
diff --git a/apps/web/playwright/reschedule.e2e.ts b/apps/web/playwright/reschedule.e2e.ts
index 70a648345f..6bab7bd4b6 100644
--- a/apps/web/playwright/reschedule.e2e.ts
+++ b/apps/web/playwright/reschedule.e2e.ts
@@ -4,6 +4,7 @@ import { BookingStatus } from "@prisma/client";
import prisma from "@calcom/prisma";
import { test } from "./lib/fixtures";
+import { testBothBookers } from "./lib/new-booker";
import { selectFirstAvailableTimeSlotNextMonth } from "./lib/testUtils";
const IS_STRIPE_ENABLED = !!(
@@ -16,7 +17,7 @@ test.describe.configure({ mode: "parallel" });
test.afterEach(({ users }) => users.deleteAll());
-test.describe("Reschedule Tests", async () => {
+testBothBookers.describe("Reschedule Tests", async () => {
test("Should do a booking request reschedule from /bookings", async ({ page, users, bookings }) => {
const user = await users.create();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 204a9fb4aa..a24a616af8 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1697,6 +1697,15 @@
"spot_popular_event_types_description": "See which of your event types are receiving the most clicks and bookings",
"no_responses_yet": "No responses yet",
"this_will_be_the_placeholder": "This will be the placeholder",
+ "error_booking_event": "An error occured when booking the event, please refresh the page and try again",
+ "timeslot_missing_title": "No timeslot selected",
+ "timeslot_missing_description": "Please select a timeslot to book the event.",
+ "timeslot_missing_cta": "Select timeslot",
+ "switch_monthly": "Switch to monthly view",
+ "switch_weekly": "Switch to weekly view",
+ "switch_multiday": "Switch to day view",
+ "num_locations": "{{num}} location options",
+ "select_on_next_step": "Select on the next step",
"this_meeting_has_not_started_yet": "This meeting has not started yet",
"this_app_requires_connected_account": "{{appName}} requires a connected {{dependencyName}} account",
"connect_app": "Connect {{dependencyName}}",
diff --git a/apps/web/test/lib/parseZone.test.ts b/apps/web/test/lib/parseZone.test.ts
index d6fee7943a..0883283da9 100644
--- a/apps/web/test/lib/parseZone.test.ts
+++ b/apps/web/test/lib/parseZone.test.ts
@@ -1,4 +1,4 @@
-import { parseZone } from "@lib/parseZone";
+import { parseZone } from "@calcom/lib/parse-zone";
const EXPECTED_DATE_STRING = "2021-06-20T11:59:59+02:00";
diff --git a/jest.config.ts b/jest.config.ts
index a7b3448f64..9fe67b3e5a 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -1,5 +1,8 @@
import type { Config } from "jest";
+// Added +2 to ensure we need to do some conversions in our tests
+process.env.TZ = "GMT+2";
+
const config: Config = {
preset: "ts-jest",
verbose: true,
@@ -66,6 +69,22 @@ const config: Config = {
transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"],
testEnvironment: "jsdom",
},
+ {
+ displayName: "@calcom/features",
+ roots: ["/packages/features"],
+ testMatch: ["**/*.(spec|test).(ts|tsx|js)"],
+ transform: {
+ "^.+\\.ts?$": "ts-jest",
+ },
+ transformIgnorePatterns: ["/node_modules/", "^.+\\.module\\.(css|sass|scss)$"],
+ testEnvironment: "jsdom",
+ moduleDirectories: ["node_modules", ""],
+ globals: {
+ "ts-jest": {
+ tsconfig: "/packages/features/tsconfig.json",
+ },
+ },
+ },
// FIXME: Prevent this breaking Jest when API module is missing
// {
// displayName: "@calcom/api",
diff --git a/packages/atoms/booker/Booker.tsx b/packages/atoms/booker/Booker.tsx
new file mode 100644
index 0000000000..28e050bace
--- /dev/null
+++ b/packages/atoms/booker/Booker.tsx
@@ -0,0 +1,13 @@
+import type { BookerProps } from "@calcom/features/bookings/Booker";
+import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
+
+import type { AtomsGlobalConfigProps } from "../types";
+
+type BookerAtomProps = BookerProps & AtomsGlobalConfigProps;
+
+/**
+ * @TODO Before we can turn this into a reusable atom
+ * * Use the webAppUrl coming from AtomsGlobalConfigProps to make url dynamic
+ * * Find a solution for translations
+ */
+export const Booker = (props: BookerAtomProps) => ;
diff --git a/packages/atoms/booker/booker.stories.mdx b/packages/atoms/booker/booker.stories.mdx
new file mode 100644
index 0000000000..1d89ee90fd
--- /dev/null
+++ b/packages/atoms/booker/booker.stories.mdx
@@ -0,0 +1,14 @@
+import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
+import { Title } from '@calcom/storybook/components'
+import { Icon } from "@calcom/ui";
+import { Booker } from './Booker';
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/atoms/booker/export.ts b/packages/atoms/booker/export.ts
new file mode 100644
index 0000000000..2bb3a2ac04
--- /dev/null
+++ b/packages/atoms/booker/export.ts
@@ -0,0 +1,5 @@
+/** Export file is only used for building the dist version of this Atom. */
+// import "../globals.css";
+
+export { Booker } from "./Booker";
+export * from "../types";
diff --git a/packages/atoms/booker/index.ts b/packages/atoms/booker/index.ts
new file mode 100644
index 0000000000..47fb01d585
--- /dev/null
+++ b/packages/atoms/booker/index.ts
@@ -0,0 +1 @@
+export { Booker } from "./Booker";
diff --git a/packages/atoms/build.mjs b/packages/atoms/build.mjs
new file mode 100644
index 0000000000..bd5c138cf6
--- /dev/null
+++ b/packages/atoms/build.mjs
@@ -0,0 +1,31 @@
+import path from "path";
+import { fileURLToPath } from "url";
+import { build } from "vite";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+// @TODO: Do we want to automate this by checking all dirs for export.ts?
+const libraries = [
+ {
+ entry: path.resolve(__dirname, "./booker/export.ts"),
+ fileName: "booker",
+ },
+];
+
+libraries.forEach(async (lib) => {
+ await build({
+ build: {
+ outDir: `./dist/${lib.fileName}`,
+ lib: {
+ ...lib,
+ formats: ["es", "cjs"],
+ },
+ emptyOutDir: false,
+ },
+ resolve: {
+ alias: {
+ crypto: require.resolve("rollup-plugin-node-builtins"),
+ },
+ },
+ });
+});
diff --git a/packages/atoms/globals.css b/packages/atoms/globals.css
new file mode 100644
index 0000000000..47e4bc917b
--- /dev/null
+++ b/packages/atoms/globals.css
@@ -0,0 +1,10 @@
+/*
+ * @NOTE: This file is only imported when building the component's CSS file
+ * When using this component in any Cal project, the globals are automatically imported
+ * in that project.
+ */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@import "../ui/styles/shared-globals.css";
\ No newline at end of file
diff --git a/packages/atoms/index.ts b/packages/atoms/index.ts
new file mode 100644
index 0000000000..647ca57b8d
--- /dev/null
+++ b/packages/atoms/index.ts
@@ -0,0 +1 @@
+export { Booker } from "./booker/Booker";
diff --git a/packages/atoms/package.json b/packages/atoms/package.json
new file mode 100644
index 0000000000..52cf9af7f4
--- /dev/null
+++ b/packages/atoms/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@calcom/atoms",
+ "private": true,
+ "sideEffects": false,
+ "type": "module",
+ "description": "Cal.com Atoms",
+ "authors": "Cal.com, Inc.",
+ "version": "1.0.0",
+ "scripts": {
+ "build": "node build.mjs"
+ },
+ "devDependencies": {
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@types/react": "^18.0.25",
+ "@types/react-dom": "^18.0.9",
+ "@vitejs/plugin-react": "^2.2.0",
+ "rollup-plugin-node-builtins": "^2.1.2",
+ "typescript": "^4.9.3",
+ "vite": "^3.2.4"
+ },
+ "main": "./index"
+}
diff --git a/packages/atoms/postcss.config.js b/packages/atoms/postcss.config.js
new file mode 100644
index 0000000000..a982c6414e
--- /dev/null
+++ b/packages/atoms/postcss.config.js
@@ -0,0 +1,8 @@
+const config = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
+
+export default config;
diff --git a/packages/atoms/tailwind.config.cjs b/packages/atoms/tailwind.config.cjs
new file mode 100644
index 0000000000..55b6825ae4
--- /dev/null
+++ b/packages/atoms/tailwind.config.cjs
@@ -0,0 +1,7 @@
+const base = require("@calcom/config/tailwind-preset");
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ ...base,
+ content: ["./bookings/**/*.tsx"],
+};
diff --git a/packages/atoms/tsconfig.json b/packages/atoms/tsconfig.json
new file mode 100644
index 0000000000..b8fb2a6430
--- /dev/null
+++ b/packages/atoms/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "@calcom/tsconfig/react-library.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["/*"]
+ },
+ "resolveJsonModule": true
+ },
+ "include": [".", "../types/next-auth.d.ts"],
+ "exclude": ["dist", "build", "node_modules"]
+}
diff --git a/packages/atoms/types.ts b/packages/atoms/types.ts
new file mode 100644
index 0000000000..8ebbf932ba
--- /dev/null
+++ b/packages/atoms/types.ts
@@ -0,0 +1,7 @@
+export interface AtomsGlobalConfigProps {
+ /**
+ * API endpoint for the Booker component to fetch data from,
+ * defaults to https://cal.com
+ */
+ webAppUrl?: string;
+}
diff --git a/packages/atoms/vite.config.ts b/packages/atoms/vite.config.ts
new file mode 100644
index 0000000000..0950e3ce06
--- /dev/null
+++ b/packages/atoms/vite.config.ts
@@ -0,0 +1,28 @@
+import { resolve } from "path";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ build: {
+ lib: {
+ entry: [resolve(__dirname, "booker/export.ts")],
+ name: "CalAtoms",
+ fileName: "cal-atoms",
+ },
+ rollupOptions: {
+ external: ["react", "fs", "path", "os", "react-dom"],
+ output: {
+ globals: {
+ react: "React",
+ "react-dom": "ReactDOM",
+ },
+ },
+ },
+ },
+ resolve: {
+ alias: {
+ fs: resolve("../../node_modules/rollup-plugin-node-builtins"),
+ path: resolve("../../node_modules/rollup-plugin-node-builtins"),
+ os: resolve("../../node_modules/rollup-plugin-node-builtins"),
+ },
+ },
+});
diff --git a/packages/config/package.json b/packages/config/package.json
index 99d73b4eab..77ceef3747 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -17,6 +17,7 @@
"eslint-plugin-prettier": "^4.2.1"
},
"devDependencies": {
+ "@savvywombat/tailwindcss-grid-areas": "^3.0.0",
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/line-clamp": "^0.4.0",
"@tailwindcss/typography": "^0.5.4",
diff --git a/packages/config/tailwind-preset.js b/packages/config/tailwind-preset.js
index c2dabaa8d2..2c05681336 100644
--- a/packages/config/tailwind-preset.js
+++ b/packages/config/tailwind-preset.js
@@ -10,6 +10,7 @@ module.exports = {
"../../packages/app-store/**/*{components,pages}/**/*.{js,ts,jsx,tsx}",
"../../packages/features/**/*.{js,ts,jsx,tsx}",
"../../packages/ui/**/*.{js,ts,jsx,tsx}",
+ "../../packages/atoms/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
theme: {
@@ -92,18 +93,12 @@ module.exports = {
},
keyframes: {
"fade-in-up": {
- "0%": {
- opacity: 0.75,
- transform: "translateY(20px)",
- },
- "100%": {
- opacity: 1,
- transform: "translateY(0)",
- },
+ from: { opacity: 0, transform: "translateY(10px)" },
+ to: { opacity: 1, transform: "none" },
},
},
animation: {
- "fade-in-up": "fade-in-up 0.35s cubic-bezier(.21,1.02,.73,1)",
+ "fade-in-up": "fade-in-up 600ms var(--animation-delay, 0ms) cubic-bezier(.21,1.02,.73,1) forwards",
},
boxShadow: {
dropdown: "0px 2px 6px -1px rgba(0, 0, 0, 0.08)",
@@ -152,6 +147,7 @@ module.exports = {
require("@tailwindcss/typography"),
require("tailwind-scrollbar"),
require("tailwindcss-radix")(),
+ require("@savvywombat/tailwindcss-grid-areas"),
plugin(({ addVariant }) => {
addVariant("mac", ".mac &");
addVariant("windows", ".windows &");
diff --git a/packages/features/bookings/Booker/Booker.tsx b/packages/features/bookings/Booker/Booker.tsx
new file mode 100644
index 0000000000..1815b7ed27
--- /dev/null
+++ b/packages/features/bookings/Booker/Booker.tsx
@@ -0,0 +1,213 @@
+import type { MotionStyle } from "framer-motion";
+import { LazyMotion, domAnimation, m, AnimatePresence } from "framer-motion";
+import { Fragment, useEffect, useRef } from "react";
+import StickyBox from "react-sticky-box";
+import { shallow } from "zustand/shallow";
+
+import classNames from "@calcom/lib/classNames";
+import useGetBrandingColours from "@calcom/lib/getBrandColours";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
+import { Logo, ToggleGroup, useCalcomTheme } from "@calcom/ui";
+import { Calendar, Columns, Grid } from "@calcom/ui/components/icon";
+
+import { AvailableTimeSlots } from "./components/AvailableTimeSlots";
+import { Away } from "./components/Away";
+import { BookEventForm } from "./components/BookEventForm";
+import { DatePicker } from "./components/DatePicker";
+import { EventMeta } from "./components/EventMeta";
+import { LargeCalendar } from "./components/LargeCalendar";
+import { BookerSection } from "./components/Section";
+import { fadeInUp, fadeInLeft, resizeAnimationConfig } from "./config";
+import { useBookerStore, useInitializeBookerStore } from "./store";
+import type { BookerLayout, BookerProps } from "./types";
+import { useEvent } from "./utils/event";
+
+const useBrandColors = ({ brandColor, darkBrandColor }: { brandColor?: string; darkBrandColor?: string }) => {
+ const brandTheme = useGetBrandingColours({
+ lightVal: brandColor,
+ darkVal: darkBrandColor,
+ });
+ useCalcomTheme(brandTheme);
+};
+
+const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: BookerProps) => {
+ const { t } = useLocale();
+ const isMobile = useMediaQuery("(max-width: 768px)");
+ const isTablet = useMediaQuery("(max-width: 1024px)");
+ const timeslotsRef = useRef(null);
+ const StickyOnDesktop = isMobile ? "div" : StickyBox;
+ const rescheduleUid =
+ typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("rescheduleUid") : null;
+ const event = useEvent();
+ const [layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow);
+ const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow);
+ const selectedDate = useBookerStore((state) => state.selectedDate);
+ const [selectedTimeslot, setSelectedTimeslot] = useBookerStore(
+ (state) => [state.selectedTimeslot, state.setSelectedTimeslot],
+ shallow
+ );
+
+ useBrandColors({
+ brandColor: event.data?.profile.brandColor,
+ darkBrandColor: event.data?.profile.darkBrandColor,
+ });
+
+ useInitializeBookerStore({
+ username,
+ eventSlug,
+ month,
+ eventId: event?.data?.id,
+ rescheduleUid,
+ rescheduleBooking,
+ });
+
+ useEffect(() => {
+ setLayout(isMobile ? "mobile" : "small_calendar");
+ }, [isMobile, setLayout]);
+
+ useEffect(() => {
+ if (event.isLoading) return setBookerState("loading");
+ if (!selectedDate) return setBookerState("selecting_date");
+ if (!selectedTimeslot) return setBookerState("selecting_time");
+ return setBookerState("booking");
+ }, [event, selectedDate, selectedTimeslot, setBookerState]);
+
+ useEffect(() => {
+ if (layout === "mobile") {
+ timeslotsRef.current?.scrollIntoView({ behavior: "smooth" });
+ }
+ }, [layout, selectedDate]);
+
+ return (
+ <>
+ {/*
+ If we would render this on mobile, it would unset the mobile variant,
+ since that's not a valid option, so it would set the layout to null.
+ */}
+ {!isMobile && (
+
+ setLayout(layout as BookerLayout)}
+ defaultValue="small_calendar"
+ options={[
+ {
+ value: "small_calendar",
+ label: ,
+ tooltip: t("switch_monthly"),
+ },
+ {
+ value: "large_calendar",
+ label: ,
+ tooltip: t("switch_weekly"),
+ },
+ {
+ value: "large_timeslots",
+ label: ,
+ tooltip: t("switch_multiday"),
+ },
+ ]}
+ />
+
+ )}
+
+
+
+
+
+
+ {layout !== "small_calendar" && !(layout === "mobile" && bookerState === "booking") && (
+
+
+
+ )}
+
+
+
+
+ setSelectedTimeslot(null)} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export const Booker = (props: BookerProps) => {
+ if (props.isAway) return ;
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx b/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx
new file mode 100644
index 0000000000..8cb8711a04
--- /dev/null
+++ b/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx
@@ -0,0 +1,78 @@
+import { useMemo } from "react";
+
+import dayjs from "@calcom/dayjs";
+import { AvailableTimes, AvailableTimesSkeleton } from "@calcom/features/bookings";
+import { useSlotsForMultipleDates } from "@calcom/features/schedules/lib/use-schedule/useSlotsForDate";
+import { classNames } from "@calcom/lib";
+
+import { useBookerStore } from "../store";
+import { useScheduleForEvent } from "../utils/event";
+
+type AvailableTimeSlotsProps = {
+ extraDays?: number;
+ limitHeight?: boolean;
+ seatsPerTimeslot?: number | null;
+};
+
+/**
+ * Renders available time slots for a given date.
+ * It will extract the date from the booker store.
+ * Next to that you can also pass in the `extraDays` prop, this
+ * will also fetch the next `extraDays` days and show multiple days
+ * in columns next to each other.
+ */
+export const AvailableTimeSlots = ({ extraDays, limitHeight, seatsPerTimeslot }: AvailableTimeSlotsProps) => {
+ const selectedDate = useBookerStore((state) => state.selectedDate);
+ const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
+ const date = selectedDate || dayjs().format("YYYY-MM-DD");
+
+ const schedule = useScheduleForEvent({
+ prefetchNextMonth: !!extraDays && dayjs(date).month() !== dayjs(date).add(extraDays, "day").month(),
+ });
+
+ // Creates an array of dates to fetch slots for.
+ // If `extraDays` is passed in, we will extend the array with the next `extraDays` days.
+ const dates = useMemo(
+ () =>
+ !extraDays
+ ? [date]
+ : [
+ // If NO date is selected yet, we show by default the upcomming `nextDays` days.
+ date,
+ ...Array.from({ length: extraDays }).map((_, index) =>
+ dayjs(date)
+ .add(index + 1, "day")
+ .format("YYYY-MM-DD")
+ ),
+ ],
+ [date, extraDays]
+ );
+
+ const isMultipleDates = dates.length > 1;
+ const slotsPerDay = useSlotsForMultipleDates(dates, schedule?.data?.slots);
+
+ return (
+
+ {schedule.isLoading
+ ? // Shows exact amount of days as skeleton.
+ Array.from({ length: 1 + (extraDays ?? 0) }).map((_, i) =>
)
+ : slotsPerDay.length > 0 &&
+ slotsPerDay.map((slots) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/features/bookings/Booker/components/Away.tsx b/packages/features/bookings/Booker/components/Away.tsx
new file mode 100644
index 0000000000..45109cc97c
--- /dev/null
+++ b/packages/features/bookings/Booker/components/Away.tsx
@@ -0,0 +1,20 @@
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+
+export const Away = () => {
+ const { t } = useLocale();
+
+ return (
+
+
+
+
+
+
😴{" " + t("user_away")}
+
{t("user_away_description")}
+
+
+
+
+
+ );
+};
diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx
new file mode 100644
index 0000000000..27f07f6d4f
--- /dev/null
+++ b/packages/features/bookings/Booker/components/BookEventForm/BookEventForm.tsx
@@ -0,0 +1,342 @@
+import { zodResolver } from "@hookform/resolvers/zod";
+import type { UseMutationResult } from "@tanstack/react-query";
+import { useMutation } from "@tanstack/react-query";
+import { useRouter } from "next/router";
+import { useMemo } from "react";
+import type { FieldError } from "react-hook-form";
+import { useForm } from "react-hook-form";
+import type { TFunction } from "react-i18next";
+import { z } from "zod";
+
+import type { EventLocationType } from "@calcom/app-store/locations";
+import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
+import dayjs from "@calcom/dayjs";
+import {
+ useTimePreferences,
+ mapBookingToMutationInput,
+ createBooking,
+ createRecurringBooking,
+ mapRecurringBookingToMutationInput,
+} from "@calcom/features/bookings/lib";
+import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
+import getBookingResponsesSchema from "@calcom/features/bookings/lib/getBookingResponsesSchema";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { HttpError } from "@calcom/lib/http-error";
+import { Form, Button, Alert, EmptyScreen } from "@calcom/ui";
+import { Calendar } from "@calcom/ui/components/icon";
+
+import { useBookerStore } from "../../store";
+import { useEvent } from "../../utils/event";
+import { getQueryParam } from "../../utils/query-param";
+import { BookingFields } from "./BookingFields";
+import { FormSkeleton } from "./Skeleton";
+
+type BookEventFormProps = {
+ onCancel?: () => void;
+};
+
+const getSuccessPath = ({
+ uid,
+ email,
+ slug,
+ formerTime,
+ isRecurring,
+}: {
+ uid: string;
+ email: string;
+ slug: string;
+ formerTime?: string;
+ isRecurring: boolean;
+}) => ({
+ pathname: `/booking/${uid}`,
+ query: {
+ [isRecurring ? "allRemainingBookings" : "isSuccessBookingPage"]: true,
+ email: email,
+ eventTypeSlug: slug,
+ formerTime: formerTime,
+ },
+});
+
+export const BookEventForm = ({ onCancel }: BookEventFormProps) => {
+ const router = useRouter();
+ const { t, i18n } = useLocale();
+ const { timezone } = useTimePreferences();
+ const rescheduleUid = useBookerStore((state) => state.rescheduleUid);
+ const rescheduleBooking = useBookerStore((state) => state.rescheduleBooking);
+ const eventSlug = useBookerStore((state) => state.eventSlug);
+ const duration = useBookerStore((state) => state.selectedDuration);
+ const timeslot = useBookerStore((state) => state.selectedTimeslot);
+ const recurringEventCount = useBookerStore((state) => state.recurringEventCount);
+ const username = useBookerStore((state) => state.username);
+ const isRescheduling = !!rescheduleUid && !!rescheduleBooking;
+ const event = useEvent();
+ const eventType = event.data;
+
+ const defaultValues = useMemo(() => {
+ if (!eventType?.bookingFields) {
+ return {};
+ }
+
+ const defaultUserValues = {
+ email: rescheduleUid ? rescheduleBooking?.attendees[0].email : getQueryParam("email") || "",
+ name: rescheduleUid ? rescheduleBooking?.attendees[0].name : getQueryParam("name") || "",
+ };
+
+ if (!isRescheduling) {
+ const defaults = {
+ responses: {} as Partial["responses"]>,
+ };
+
+ const responses = eventType.bookingFields.reduce((responses, field) => {
+ return {
+ ...responses,
+ [field.name]: getQueryParam(field.name) || undefined,
+ };
+ }, {});
+ defaults.responses = {
+ ...responses,
+ name: defaultUserValues.name,
+ email: defaultUserValues.email,
+ };
+
+ return defaults;
+ }
+
+ if (!rescheduleBooking || !rescheduleBooking.attendees.length) {
+ return {};
+ }
+ const primaryAttendee = rescheduleBooking.attendees[0];
+ if (!primaryAttendee) {
+ return {};
+ }
+
+ const defaults = {
+ responses: {} as Partial["responses"]>,
+ };
+
+ const responses = eventType.bookingFields.reduce((responses, field) => {
+ return {
+ ...responses,
+ [field.name]: rescheduleBooking.responses[field.name],
+ };
+ }, {});
+ defaults.responses = {
+ ...responses,
+ name: defaultUserValues.name,
+ email: defaultUserValues.email,
+ };
+ return defaults;
+ }, [eventType?.bookingFields, isRescheduling, rescheduleBooking, rescheduleUid]);
+
+ const bookingFormSchema = z
+ .object({
+ responses: event?.data
+ ? getBookingResponsesSchema({
+ eventType: { bookingFields: getBookingFieldsWithSystemFields(event.data) },
+ view: rescheduleUid ? "reschedule" : "booking",
+ })
+ : // Fallback until event is loaded.
+ z.object({}),
+ })
+ .passthrough();
+
+ type BookingFormValues = {
+ locationType?: EventLocationType["type"];
+ responses: z.infer["responses"];
+ // Key is not really part of form values, but only used to have a key
+ // to set generic error messages on. Needed until RHF has implemented root error keys.
+ globalError: undefined;
+ };
+
+ const bookingForm = useForm({
+ defaultValues,
+ resolver: zodResolver(bookingFormSchema), // Since this isn't set to strict we only validate the fields in the schema
+ });
+
+ const createBookingMutation = useMutation(createBooking, {
+ onSuccess: async (responseData) => {
+ const { uid, paymentUid } = responseData;
+ if (paymentUid) {
+ return await router.push(
+ createPaymentLink({
+ paymentUid,
+ date: timeslot,
+ name: bookingForm.getValues("responses.name"),
+ email: bookingForm.getValues("responses.email"),
+ absolute: false,
+ })
+ );
+ }
+
+ if (!uid) {
+ console.error("No uid returned from createBookingMutation");
+ return;
+ }
+
+ return await router.push(
+ getSuccessPath({
+ uid,
+ email: bookingForm.getValues("responses.email"),
+ formerTime: rescheduleBooking?.startTime
+ ? dayjs(rescheduleBooking?.startTime).toISOString()
+ : undefined,
+ slug: `${eventSlug}`,
+ isRecurring: false,
+ })
+ );
+ },
+ });
+
+ const createRecurringBookingMutation = useMutation(createRecurringBooking, {
+ onSuccess: async (responseData) => {
+ const { uid } = responseData[0] || {};
+
+ if (!uid) {
+ console.error("No uid returned from createRecurringBookingMutation");
+ return;
+ }
+
+ return await router.push(
+ getSuccessPath({
+ uid,
+ email: bookingForm.getValues("responses.email"),
+ slug: `${eventSlug}`,
+ isRecurring: true,
+ })
+ );
+ },
+ });
+
+ if (event.isError) return ;
+ if (event.isLoading || !event.data) return ;
+ if (!timeslot)
+ return (
+
+ );
+
+ const bookEvent = (values: BookingFormValues) => {
+ bookingForm.clearErrors();
+
+ // It shouldn't be possible that this method is fired without having event data,
+ // but since in theory (looking at the types) it is possible, we still handle that case.
+ if (!event?.data) {
+ bookingForm.setError("globalError", { message: t("error_booking_event") });
+ return;
+ }
+
+ // Ensures that duration is an allowed value, if not it defaults to the
+ // default event duration.
+ const validDuration =
+ duration &&
+ event.data.metadata?.multipleDuration &&
+ event.data.metadata?.multipleDuration.includes(duration)
+ ? duration
+ : event.data.length;
+
+ const bookingInput = {
+ values,
+ duration: validDuration,
+ event: event.data,
+ date: timeslot,
+ timeZone: timezone,
+ language: i18n.language,
+ rescheduleUid: rescheduleUid || undefined,
+ username: username || "",
+ metadata: Object.keys(router.query)
+ .filter((key) => key.startsWith("metadata"))
+ .reduce(
+ (metadata, key) => ({
+ ...metadata,
+ [key.substring("metadata[".length, key.length - 1)]: router.query[key],
+ }),
+ {}
+ ),
+ };
+
+ if (event.data?.recurringEvent?.freq && recurringEventCount) {
+ createRecurringBookingMutation.mutate(
+ mapRecurringBookingToMutationInput(bookingInput, recurringEventCount)
+ );
+ } else {
+ createBookingMutation.mutate(mapBookingToMutationInput(bookingInput));
+ }
+ };
+
+ if (!eventType) {
+ console.warn("No event type found for event", router.query);
+ return ;
+ }
+
+ return (
+
+
+ {(createBookingMutation.isError ||
+ createRecurringBookingMutation.isError ||
+ bookingForm.formState.errors["globalError"]) && (
+
+ )}
+
+ );
+};
+
+const getError = (
+ globalError: FieldError | undefined,
+ // It feels like an implementation detail to reimplement the types of useMutation here.
+ // Since they don't matter for this function, I'd rather disable them then giving you
+ // the cognitive overload of thinking to update them here when anything changes.
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ bookingMutation: UseMutationResult,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ recurringBookingMutation: UseMutationResult,
+ t: TFunction
+) => {
+ if (globalError) return globalError.message;
+
+ const error = bookingMutation.error || recurringBookingMutation.error;
+
+ return error instanceof HttpError || error instanceof Error ? (
+ <>{t("can_you_try_again")}>
+ ) : (
+ "Unknown error"
+ );
+};
diff --git a/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx b/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx
new file mode 100644
index 0000000000..6ba16a63e1
--- /dev/null
+++ b/packages/features/bookings/Booker/components/BookEventForm/BookingFields.tsx
@@ -0,0 +1,119 @@
+import { useFormContext } from "react-hook-form";
+
+import type { LocationObject } from "@calcom/app-store/locations";
+import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
+import { FormBuilderField } from "@calcom/features/form-builder";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import type { RouterOutputs } from "@calcom/trpc/react";
+
+import { SystemField } from "../../../lib/getBookingFields";
+
+export const BookingFields = ({
+ fields,
+ locations,
+ rescheduleUid,
+ isDynamicGroupBooking,
+}: {
+ fields: NonNullable["bookingFields"];
+ locations: LocationObject[];
+ rescheduleUid?: string;
+ isDynamicGroupBooking: boolean;
+}) => {
+ const { t } = useLocale();
+ const { watch, setValue } = useFormContext();
+ const locationResponse = watch("responses.location");
+ const currentView = rescheduleUid ? "reschedule" : "";
+
+ return (
+ // TODO: It might make sense to extract this logic into BookingFields config, that would allow to quickly configure system fields and their editability in fresh booking and reschedule booking view
+
+ {fields.map((field, index) => {
+ // 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;
+
+ let noLabel = false;
+ let hidden = !!field.hidden;
+ const fieldViews = field.views;
+
+ if (fieldViews && !fieldViews.find((view) => view.id === currentView)) {
+ return null;
+ }
+
+ if (field.name === SystemField.Enum.rescheduleReason) {
+ // rescheduleReason is a reschedule specific field and thus should be editable during reschedule
+ readOnly = false;
+ }
+
+ if (field.name === SystemField.Enum.smsReminderNumber) {
+ // `smsReminderNumber` and location.optionValue when location.value===phone are the same data point. We should solve it in a better way in the Form Builder itself.
+ // I think we should have a way to connect 2 fields together and have them share the same value in Form Builder
+ if (locationResponse?.value === "phone") {
+ setValue(`responses.${SystemField.Enum.smsReminderNumber}`, locationResponse?.optionValue);
+ // Just don't render the field now, as the value is already connected to attendee phone location
+ return null;
+ }
+ // `smsReminderNumber` can be edited during reschedule even though it's a system field
+ readOnly = false;
+ }
+
+ if (field.name === SystemField.Enum.guests) {
+ // No matter what user configured for Guests field, we don't show it for dynamic group booking as that doesn't support guests
+ hidden = isDynamicGroupBooking ? true : !!field.hidden;
+ }
+
+ // We don't show `notes` field during reschedule
+ if (
+ (field.name === SystemField.Enum.notes || field.name === SystemField.Enum.guests) &&
+ !!rescheduleUid
+ ) {
+ return null;
+ }
+
+ // Dynamically populate location field options
+ if (field.name === SystemField.Enum.location && field.type === "radioInput") {
+ if (!field.optionsInputs) {
+ throw new Error("radioInput must have optionsInputs");
+ }
+ const optionsInputs = field.optionsInputs;
+
+ // TODO: Instead of `getLocationOptionsForSelect` options should be retrieved from dataStore[field.getOptionsAt]. It would make it agnostic of the `name` of the field.
+ const options = getLocationOptionsForSelect(locations, t);
+ options.forEach((option) => {
+ const optionInput = optionsInputs[option.value as keyof typeof optionsInputs];
+ if (optionInput) {
+ optionInput.placeholder = option.inputPlaceholder;
+ }
+ });
+
+ field.options = options.filter(
+ (location): location is NonNullable<(typeof options)[number]> => !!location
+ );
+ // If we have only one option and it has an input, we don't show the field label because Option name acts as label.
+ // e.g. If it's just Attendee Phone Number option then we don't show `Location` label
+ if (field.options.length === 1) {
+ if (field.optionsInputs[field.options[0].value]) {
+ noLabel = true;
+ } else {
+ // If there's only one option and it doesn't have an input, we don't show the field at all because it's visible in the left side bar
+ hidden = true;
+ }
+ }
+ }
+
+ const label = noLabel ? "" : field.label || t(field.defaultLabel || "");
+ const placeholder = field.placeholder || t(field.defaultPlaceholder || "");
+
+ return (
+
+ );
+ })}
+
+ );
+};
diff --git a/packages/features/bookings/Booker/components/BookEventForm/Skeleton.tsx b/packages/features/bookings/Booker/components/BookEventForm/Skeleton.tsx
new file mode 100644
index 0000000000..5da8cbabd5
--- /dev/null
+++ b/packages/features/bookings/Booker/components/BookEventForm/Skeleton.tsx
@@ -0,0 +1,29 @@
+import { SkeletonText } from "@calcom/ui";
+
+export const FormSkeleton = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/packages/features/bookings/Booker/components/BookEventForm/index.ts b/packages/features/bookings/Booker/components/BookEventForm/index.ts
new file mode 100644
index 0000000000..323719ddb6
--- /dev/null
+++ b/packages/features/bookings/Booker/components/BookEventForm/index.ts
@@ -0,0 +1 @@
+export { BookEventForm } from "./BookEventForm";
diff --git a/packages/features/bookings/Booker/components/DatePicker.tsx b/packages/features/bookings/Booker/components/DatePicker.tsx
new file mode 100644
index 0000000000..e0679b73f6
--- /dev/null
+++ b/packages/features/bookings/Booker/components/DatePicker.tsx
@@ -0,0 +1,54 @@
+import { useEffect, useState } from "react";
+import { shallow } from "zustand/shallow";
+
+import type { Dayjs } from "@calcom/dayjs";
+import dayjs from "@calcom/dayjs";
+import { default as DatePickerComponent } from "@calcom/features/calendars/DatePicker";
+import { useNonEmptyScheduleDays } from "@calcom/features/schedules";
+import { weekdayToWeekIndex } from "@calcom/lib/date-fns";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+
+import { useBookerStore } from "../store";
+import { useEvent, useScheduleForEvent } from "../utils/event";
+
+export const DatePicker = () => {
+ const [isLoadedClientSide, setIsLoadedClientSide] = useState(false);
+ const { i18n } = useLocale();
+ const [month, selectedDate] = useBookerStore((state) => [state.month, state.selectedDate], shallow);
+ const [setSelectedDate, setMonth] = useBookerStore(
+ (state) => [state.setSelectedDate, state.setMonth],
+ shallow
+ );
+ const event = useEvent();
+ const schedule = useScheduleForEvent();
+ const nonEmptyScheduleDays = useNonEmptyScheduleDays(schedule?.data?.slots);
+
+ // Not rendering the component on the server side to avoid hydration issues
+ // @TODO: We should update the datepicker component as soon as the current booker isn't
+ // used anymore, so we don't need to have this check.
+ useEffect(() => {
+ setIsLoadedClientSide(true);
+ }, []);
+
+ if (!isLoadedClientSide) return null;
+
+ return (
+
+ {
+ setSelectedDate(date.format("YYYY-MM-DD"));
+ }}
+ onMonthChange={(date: Dayjs) => {
+ setMonth(date.format("YYYY-MM"));
+ setSelectedDate(date.format("YYYY-MM-DD"));
+ }}
+ includedDates={nonEmptyScheduleDays}
+ locale={i18n.language}
+ browsingDate={month ? dayjs(month) : undefined}
+ selected={dayjs(selectedDate)}
+ weekStart={weekdayToWeekIndex(event?.data?.users?.[0]?.weekStart)}
+ />
+
+ );
+};
diff --git a/packages/features/bookings/Booker/components/EventMeta.tsx b/packages/features/bookings/Booker/components/EventMeta.tsx
new file mode 100644
index 0000000000..b2b05f5856
--- /dev/null
+++ b/packages/features/bookings/Booker/components/EventMeta.tsx
@@ -0,0 +1,93 @@
+import { m } from "framer-motion";
+import dynamic from "next/dynamic";
+
+import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calcom/features/bookings";
+import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
+import { useTimePreferences } from "@calcom/features/bookings/lib";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Calendar, Globe } from "@calcom/ui/components/icon";
+
+import { fadeInUp } from "../config";
+import { useBookerStore } from "../store";
+import { formatEventFromToTime } from "../utils/dates";
+import { useEvent } from "../utils/event";
+
+const TimezoneSelect = dynamic(() => import("@calcom/ui").then((mod) => mod.TimezoneSelect), {
+ ssr: false,
+});
+
+export const EventMeta = () => {
+ const { timezone, setTimezone, timeFormat } = useTimePreferences();
+ const selectedDuration = useBookerStore((state) => state.selectedDuration);
+ const selectedTimeslot = useBookerStore((state) => state.selectedTimeslot);
+ const bookerState = useBookerStore((state) => state.state);
+ const rescheduleBooking = useBookerStore((state) => state.rescheduleBooking);
+ const { i18n, t } = useLocale();
+ const { data: event, isLoading } = useEvent();
+
+ return (
+
+ {isLoading && (
+
+
+
+ )}
+ {!isLoading && !!event && (
+
+
+ {event?.title}
+
+ {rescheduleBooking && (
+
+ {t("former_time")}
+
+
+ {formatEventFromToTime(
+ rescheduleBooking.startTime.toString(),
+ null,
+ timeFormat,
+ timezone,
+ i18n.language
+ )}
+
+
+ )}
+ {selectedTimeslot && (
+
+ {formatEventFromToTime(
+ selectedTimeslot,
+ selectedDuration,
+ timeFormat,
+ timezone,
+ i18n.language
+ )}
+
+ )}
+
+
+ {bookerState === "booking" ? (
+ <>{timezone}>
+ ) : (
+
+ "!min-h-0 p-0 border-0 bg-transparent focus-within:ring-0",
+ menu: () => "!w-64 max-w-[90vw]",
+ singleValue: () => "text-text py-1",
+ }}
+ value={timezone}
+ onChange={(tz) => setTimezone(tz.value)}
+ />
+
+ )}
+
+
+
+ )}
+
+ );
+};
diff --git a/packages/features/bookings/Booker/components/LargeCalendar.tsx b/packages/features/bookings/Booker/components/LargeCalendar.tsx
new file mode 100644
index 0000000000..155e9c6a94
--- /dev/null
+++ b/packages/features/bookings/Booker/components/LargeCalendar.tsx
@@ -0,0 +1,30 @@
+import { shallow } from "zustand/shallow";
+
+import dayjs from "@calcom/dayjs";
+
+import { useBookerStore } from "../store";
+
+export const LargeCalendar = () => {
+ const [setSelectedDate, setSelectedTimeslot] = useBookerStore(
+ (state) => [state.setSelectedDate, state.setSelectedTimeslot],
+ shallow
+ );
+
+ return (
+
+ Something big is coming...
+
+ {
+ ev.preventDefault();
+ setSelectedDate(dayjs().format("YYYY-MM-DD"));
+ setSelectedTimeslot(dayjs().format());
+ }}>
+ Click this button to set date + time in one go just like the big thing that is coming here would do.
+ :)
+
+
+ );
+};
diff --git a/packages/features/bookings/Booker/components/Section.tsx b/packages/features/bookings/Booker/components/Section.tsx
new file mode 100644
index 0000000000..4c1a039d1a
--- /dev/null
+++ b/packages/features/bookings/Booker/components/Section.tsx
@@ -0,0 +1,62 @@
+import type { MotionProps } from "framer-motion";
+import { m } from "framer-motion";
+import { forwardRef } from "react";
+
+import { classNames } from "@calcom/lib";
+
+import { useBookerStore } from "../store";
+import type { BookerAreas, BookerLayout } from "../types";
+
+/**
+ * Define what grid area a section should be in.
+ * Value is either a string (in case it's always the same area), or an object
+ * looking like:
+ * {
+ * // Where default is the required default area.
+ * default: "calendar",
+ * // Any optional overrides for different layouts by their layout name.
+ * large_calendar: "main",
+ * }
+ */
+type GridArea = BookerAreas | ({ [key in BookerLayout]?: BookerAreas } & { default: BookerAreas });
+
+type BookerSectionProps = {
+ children: React.ReactNode;
+ area: GridArea;
+ visible?: boolean;
+ className?: string;
+} & MotionProps;
+
+// This map with strings is needed so Tailwind generates all classnames,
+// If we would concatenate them with JS, Tailwind would not generate them.
+const gridAreaClassNameMap: { [key in BookerAreas]: string } = {
+ calendar: "[grid-area:calendar]",
+ main: "[grid-area:main]",
+ meta: "[grid-area:meta]",
+ timeslots: "[grid-area:timeslots]",
+};
+
+/**
+ * Small helper compnent that renders a booker section in a specific grid area.
+ */
+export const BookerSection = forwardRef(function BookerSection(
+ { children, area, visible, className, ...props },
+ ref
+) {
+ const layout = useBookerStore((state) => state.layout);
+ let gridClassName: string;
+
+ if (typeof area === "string") {
+ gridClassName = gridAreaClassNameMap[area];
+ } else {
+ gridClassName = gridAreaClassNameMap[area[layout] || area.default];
+ }
+
+ if (!visible && typeof visible !== "undefined") return null;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/features/bookings/Booker/config.ts b/packages/features/bookings/Booker/config.ts
new file mode 100644
index 0000000000..33a9c9aff7
--- /dev/null
+++ b/packages/features/bookings/Booker/config.ts
@@ -0,0 +1,82 @@
+import type { TargetAndTransition } from "framer-motion";
+
+import type { BookerLayout, BookerState } from "./types";
+
+// Framer motion fade in animation configs.
+export const fadeInLeft = {
+ variants: {
+ visible: { opacity: 1, x: 0 },
+ hidden: { opacity: 0, x: 20 },
+ },
+ initial: "hidden",
+ exit: "hidden",
+ animate: "visible",
+ transition: { ease: "easeInOut", delay: 0.1 },
+};
+export const fadeInUp = {
+ variants: {
+ visible: { opacity: 1, y: 0 },
+ hidden: { opacity: 0, y: 20 },
+ },
+ initial: "hidden",
+ exit: "hidden",
+ animate: "visible",
+ transition: { ease: "easeInOut", delay: 0.1 },
+};
+
+type ResizeAnimationConfig = {
+ [key in BookerLayout]: {
+ [key in BookerState | "default"]?: TargetAndTransition;
+ };
+};
+
+/**
+ * This configuration is used to animate the grid container for the booker.
+ * The object is structured as following:
+ *
+ * The root property of the object: is the name of the layout
+ * (mobile, small_calendar, large_calendar, large_timeslots)
+ *
+ * The values of these properties are objects that define the animation for each state of the booker.
+ * The animation have the same properties as you could pass to the animate prop of framer-motion:
+ * @see: https://www.framer.com/motion/animation/
+ */
+export const resizeAnimationConfig: ResizeAnimationConfig = {
+ mobile: {
+ default: {
+ width: "100%",
+ gridTemplateAreas: `
+ "meta"
+ "main"
+ "timeslots"
+ `,
+ gridTemplateColumns: "100%",
+ },
+ },
+ small_calendar: {
+ default: {
+ width: "calc(var(--booker-meta-width) + var(--booker-main-width))",
+ gridTemplateAreas: `"meta main"`,
+ gridTemplateColumns: "var(--booker-meta-width) var(--booker-main-width)",
+ },
+ selecting_time: {
+ width: "calc(var(--booker-meta-width) + var(--booker-main-width) + var(--booker-timeslots-width))",
+ gridTemplateAreas: `"meta main timeslots"`,
+ gridTemplateColumns: "var(--booker-meta-width) var(--booker-main-width) var(--booker-timeslots-width)",
+ },
+ },
+ large_calendar: {
+ default: {
+ width: "100%",
+ gridTemplateAreas: `"meta main"`,
+ gridTemplateColumns: "var(--booker-meta-width) 1fr",
+ },
+ },
+ large_timeslots: {
+ default: {
+ width: "100%",
+ gridTemplateAreas: `"meta main"`,
+ gridTemplateColumns: "var(--booker-meta-width) 1fr",
+ },
+ },
+};
diff --git a/packages/features/bookings/Booker/index.ts b/packages/features/bookings/Booker/index.ts
new file mode 100644
index 0000000000..8d7ec08eec
--- /dev/null
+++ b/packages/features/bookings/Booker/index.ts
@@ -0,0 +1,2 @@
+export { Booker } from "./Booker";
+export type { BookerProps } from "./types";
diff --git a/packages/features/bookings/Booker/store.ts b/packages/features/bookings/Booker/store.ts
new file mode 100644
index 0000000000..7716e575c1
--- /dev/null
+++ b/packages/features/bookings/Booker/store.ts
@@ -0,0 +1,168 @@
+import { useEffect } from "react";
+import { create } from "zustand";
+
+import dayjs from "@calcom/dayjs";
+
+import type { GetBookingType } from "../lib/get-booking";
+import type { BookerState, BookerLayout } from "./types";
+import { updateQueryParam, getQueryParam } from "./utils/query-param";
+
+/**
+ * Arguments passed into store initializer, containing
+ * the event data.
+ */
+type StoreInitializeType = {
+ username: string;
+ eventSlug: string;
+ // Month can be undefined if it's not passed in as a prop.
+ month?: string;
+ eventId: number | undefined;
+ rescheduleUid: string | null;
+ rescheduleBooking: GetBookingType | null | undefined;
+};
+
+type BookerStore = {
+ /**
+ * Event details. These are stored in store for easier
+ * access in child components.
+ */
+ username: string | null;
+ eventSlug: string | null;
+ eventId: number | null;
+ /**
+ * Current month being viewed. Format is YYYY-MM.
+ */
+ month: string | null;
+ setMonth: (month: string | null) => void;
+ /**
+ * Current state of the booking process
+ * the user is currently in. See enum for possible values.
+ */
+ state: BookerState;
+ setState: (state: BookerState) => void;
+ /**
+ * The booker component supports different layouts,
+ * this value tracks the current layout.
+ */
+ layout: BookerLayout;
+ setLayout: (layout: BookerLayout) => void;
+ /**
+ * Date selected by user (exact day). Format is YYYY-MM-DD.
+ */
+ selectedDate: string | null;
+ setSelectedDate: (date: string | null) => void;
+ /**
+ * Selected event duration in minutes.
+ */
+ selectedDuration: number | null;
+ setSelectedDuration: (duration: number | null) => void;
+ /**
+ * Selected timeslot user has chosen. This is a date string
+ * containing both the date + time.
+ */
+ selectedTimeslot: string | null;
+ setSelectedTimeslot: (timeslot: string | null) => void;
+ /**
+ * Number of recurring events to create.
+ */
+ recurringEventCount: number | null;
+ setRecurringEventCount(count: number | null): void;
+ /**
+ * If booking is being rescheduled, both the ID as well as
+ * the current booking details are passed in. The `rescheduleBooking`
+ * object is something that's fetched server side.
+ */
+ rescheduleUid: string | null;
+ rescheduleBooking: GetBookingType | null;
+ /**
+ * Method called by booker component to set initial data.
+ */
+ initialize: (data: StoreInitializeType) => void;
+};
+
+/**
+ * The booker store contains the data of the component's
+ * current state. This data can be reused within child components
+ * by importing this hook.
+ *
+ * See comments in interface above for more information on it's specific values.
+ */
+export const useBookerStore = create((set, get) => ({
+ state: "loading",
+ setState: (state: BookerState) => set({ state }),
+ layout: "small_calendar",
+ setLayout: (layout: BookerLayout) => set({ layout }),
+ selectedDate: getQueryParam("date") || null,
+ setSelectedDate: (selectedDate: string | null) => {
+ set({ selectedDate });
+ updateQueryParam("date", selectedDate ?? "");
+ },
+ username: null,
+ eventSlug: null,
+ eventId: null,
+ month: getQueryParam("month") || getQueryParam("date") || dayjs().format("YYYY-MM"),
+ setMonth: (month: string | null) => {
+ set({ month, selectedTimeslot: null });
+ updateQueryParam("month", month ?? "");
+ get().setSelectedDate(null);
+ },
+ initialize: ({
+ username,
+ eventSlug,
+ month,
+ eventId,
+ rescheduleUid = null,
+ rescheduleBooking = null,
+ }: StoreInitializeType) => {
+ if (
+ get().username === username &&
+ get().eventSlug === eventSlug &&
+ get().month === month &&
+ get().eventId === eventId &&
+ get().rescheduleUid === rescheduleUid &&
+ get().rescheduleBooking?.responses.email === rescheduleBooking?.responses.email
+ )
+ return;
+ set({
+ username,
+ eventSlug,
+ eventId,
+ rescheduleUid,
+ rescheduleBooking,
+ });
+ // Unset selected timeslot if user is rescheduling. This could happen
+ // if the user reschedules a booking right after the confirmation page.
+ // In that case the time would still be store in the store, this way we
+ // force clear this.
+ if (rescheduleBooking) set({ selectedTimeslot: null });
+ if (month) set({ month });
+ },
+ selectedDuration: Number(getQueryParam("duration")) || null,
+ setSelectedDuration: (selectedDuration: number | null) => {
+ set({ selectedDuration });
+ updateQueryParam("duration", selectedDuration ?? "");
+ },
+ recurringEventCount: null,
+ setRecurringEventCount: (recurringEventCount: number | null) => set({ recurringEventCount }),
+ rescheduleBooking: null,
+ rescheduleUid: null,
+ selectedTimeslot: getQueryParam("slot") || null,
+ setSelectedTimeslot: (selectedTimeslot: string | null) => {
+ set({ selectedTimeslot });
+ updateQueryParam("slot", selectedTimeslot ?? "");
+ },
+}));
+
+export const useInitializeBookerStore = ({
+ username,
+ eventSlug,
+ month,
+ eventId,
+ rescheduleUid = null,
+ rescheduleBooking = null,
+}: StoreInitializeType) => {
+ const initializeStore = useBookerStore((state) => state.initialize);
+ useEffect(() => {
+ initializeStore({ username, eventSlug, month, eventId, rescheduleUid, rescheduleBooking });
+ }, [initializeStore, username, eventSlug, month, eventId, rescheduleUid, rescheduleBooking]);
+};
diff --git a/packages/features/bookings/Booker/types.ts b/packages/features/bookings/Booker/types.ts
new file mode 100644
index 0000000000..54687bd12c
--- /dev/null
+++ b/packages/features/bookings/Booker/types.ts
@@ -0,0 +1,46 @@
+import type { GetBookingType } from "../lib/get-booking";
+
+export interface BookerProps {
+ eventSlug: string;
+ username: string;
+
+ /**
+ * If month is NOT set as a prop on the component, we expect a query parameter
+ * called `month` to be present on the url. If that is missing, the component will
+ * default to the current month.
+ * @note In case you're using a client side router, please pass the value in as a prop,
+ * since the component will leverage window.location, which might not have the query param yet.
+ * @format YYYY-MM.
+ * @optional
+ */
+ month?: string;
+ /**
+ * Default selected date for with the slotpicker will already open.
+ * @optional
+ */
+ selectedDate?: Date;
+
+ hideBranding?: boolean;
+ /**
+ * Sets the Booker component to the away state.
+ * This is NOT revalidated by calling the API.
+ */
+ isAway?: boolean;
+ /**
+ * If false and the current username indicates a dynamic booking,
+ * the Booker will immediately show an error.
+ * This is NOT revalidated by calling the API.
+ */
+ allowsDynamicBooking?: boolean;
+ /**
+ * When rescheduling a booking, the current' bookings data is passed in via this prop.
+ * The component itself won't fetch booking data based on the ID, since there is not public
+ * api to fetch this data. Therefore rescheduling a booking currently is not possible
+ * within the atom (i.e. without a server side component).
+ */
+ rescheduleBooking?: GetBookingType;
+}
+
+export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking";
+export type BookerLayout = "small_calendar" | "large_timeslots" | "large_calendar" | "mobile";
+export type BookerAreas = "calendar" | "timeslots" | "main" | "meta";
diff --git a/packages/features/bookings/Booker/utils/dates.ts b/packages/features/bookings/Booker/utils/dates.ts
new file mode 100644
index 0000000000..7306119b08
--- /dev/null
+++ b/packages/features/bookings/Booker/utils/dates.ts
@@ -0,0 +1,18 @@
+import dayjs from "@calcom/dayjs";
+import type { TimeFormat } from "@calcom/lib/timeFormat";
+
+export const formatEventFromToTime = (
+ date: string,
+ duration: number | null,
+ timeFormat: TimeFormat,
+ timeZone: string,
+ language: string
+) => {
+ const start = dayjs(date).tz(timeZone);
+ const end = duration ? start.add(duration, "minute") : null;
+ return `${start.format("dddd")}, ${start
+ .toDate()
+ .toLocaleDateString(language, { dateStyle: "long" })} ${start.format(timeFormat)} ${
+ end ? `– ${end.format(timeFormat)}` : ``
+ }`;
+};
diff --git a/packages/features/bookings/Booker/utils/event.ts b/packages/features/bookings/Booker/utils/event.ts
new file mode 100644
index 0000000000..3bdebef3f4
--- /dev/null
+++ b/packages/features/bookings/Booker/utils/event.ts
@@ -0,0 +1,53 @@
+import { shallow } from "zustand/shallow";
+
+import { useSchedule } from "@calcom/features/schedules";
+import { trpc } from "@calcom/trpc/react";
+
+import { useTimePreferences } from "../../lib/timePreferences";
+import { useBookerStore } from "../store";
+
+/**
+ * Wrapper hook around the trpc query that fetches
+ * the event curently viewed in the booker. It will get
+ * the current event slug and username from the booker store.
+ *
+ * Using this hook means you only need to use one hook, instead
+ * of combining multiple conditional hooks.
+ */
+export const useEvent = () => {
+ const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow);
+
+ return trpc.viewer.public.event.useQuery(
+ { username: username ?? "", eventSlug: eventSlug ?? "" },
+ { refetchOnWindowFocus: false, enabled: Boolean(username) && Boolean(eventSlug) }
+ );
+};
+
+/**
+ * Gets schedule for the current event and current month.
+ * Gets all values from the booker store.
+ *
+ * Using this hook means you only need to use one hook, instead
+ * of combining multiple conditional hooks.
+ *
+ * The prefetchNextMonth argument can be used to prefetch two months at once,
+ * useful when the user is viewing dates near the end of the month,
+ * this way the multi day view will show data of both months.
+ */
+export const useScheduleForEvent = ({ prefetchNextMonth }: { prefetchNextMonth?: boolean } = {}) => {
+ const { timezone } = useTimePreferences();
+ const event = useEvent();
+ const [username, eventSlug, month] = useBookerStore(
+ (state) => [state.username, state.eventSlug, state.month],
+ shallow
+ );
+
+ return useSchedule({
+ username,
+ eventSlug,
+ eventId: event.data?.id,
+ month,
+ timezone,
+ prefetchNextMonth,
+ });
+};
diff --git a/packages/features/bookings/Booker/utils/query-param.ts b/packages/features/bookings/Booker/utils/query-param.ts
new file mode 100644
index 0000000000..efa7617371
--- /dev/null
+++ b/packages/features/bookings/Booker/utils/query-param.ts
@@ -0,0 +1,13 @@
+export const updateQueryParam = (param: string, value: string | number) => {
+ if (typeof window === "undefined") return;
+
+ const url = new URL(window.location.href);
+ url.searchParams.set(param, `${value}`);
+ window.history.pushState({}, "", url.href);
+};
+
+export const getQueryParam = (param: string) => {
+ if (typeof window === "undefined") return;
+
+ return new URLSearchParams(window.location.search).get(param);
+};
diff --git a/packages/features/bookings/components/AvailableTimes.tsx b/packages/features/bookings/components/AvailableTimes.tsx
new file mode 100644
index 0000000000..bd8ce620d3
--- /dev/null
+++ b/packages/features/bookings/components/AvailableTimes.tsx
@@ -0,0 +1,104 @@
+import type { Dayjs } from "@calcom/dayjs";
+import dayjs from "@calcom/dayjs";
+import type { Slots } from "@calcom/features/schedules";
+import { classNames } from "@calcom/lib";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { nameOfDay } from "@calcom/lib/weekday";
+import { Button, SkeletonText } from "@calcom/ui";
+
+import { useTimePreferences } from "../lib";
+import { TimeFormatToggle } from "./TimeFormatToggle";
+
+type AvailableTimesProps = {
+ date: Dayjs;
+ slots: Slots[string];
+ onTimeSelect: (time: string) => void;
+ seatsPerTimeslot?: number | null;
+ showTimeformatToggle?: boolean;
+ className?: string;
+};
+
+export const AvailableTimes = ({
+ date,
+ slots,
+ onTimeSelect,
+ seatsPerTimeslot,
+ showTimeformatToggle = true,
+ className,
+}: AvailableTimesProps) => {
+ const { t, i18n } = useLocale();
+ const [timeFormat, timezone] = useTimePreferences((state) => [state.timeFormat, state.timezone]);
+ const hasTimeSlots = !!seatsPerTimeslot;
+
+ return (
+
+
+
+
+ {nameOfDay(i18n.language, Number(date.format("d")), "short")},
+
+
+ {" "}
+ {date.toDate().toLocaleString(i18n.language, { month: "short" })} {date.format(" D ")}
+
+
+
+ {showTimeformatToggle && (
+
+
+
+ )}
+
+
+ {!slots.length && (
+
+ {t("all_booked_today")}
+
+ )}
+
+ {slots.map((slot) => {
+ const bookingFull = !!(hasTimeSlots && slot.attendees && slot.attendees >= seatsPerTimeslot);
+ return (
+
onTimeSelect(slot.time)}
+ className="mb-3 flex h-auto min-h-[44px] w-full flex-col items-center justify-center py-2"
+ color="secondary">
+ {dayjs.utc(slot.time).tz(timezone).format(timeFormat)}
+ {bookingFull && {t("booking_full")}
}
+ {hasTimeSlots && !bookingFull && (
+
+ = 0.8
+ ? "bg-rose-600"
+ : slot.attendees && slot.attendees / seatsPerTimeslot >= 0.33
+ ? "bg-yellow-500"
+ : "bg-emerald-400",
+ "mr-1 inline-block h-2 w-2 rounded-full"
+ )}
+ aria-hidden
+ />
+ {slot.attendees ? seatsPerTimeslot - slot.attendees : seatsPerTimeslot}{" "}
+ {t("seats_available")}
+
+ )}
+
+ );
+ })}
+
+
+ );
+};
+
+export const AvailableTimesSkeleton = () => (
+
+ {/* Random number of elements between 1 and 10. */}
+ {Array.from({ length: Math.floor(Math.random() * 10) + 1 }).map((_, i) => (
+
+ ))}
+
+);
diff --git a/packages/features/bookings/components/TimeFormatToggle.tsx b/packages/features/bookings/components/TimeFormatToggle.tsx
new file mode 100644
index 0000000000..71add8b05b
--- /dev/null
+++ b/packages/features/bookings/components/TimeFormatToggle.tsx
@@ -0,0 +1,25 @@
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { TimeFormat } from "@calcom/lib/timeFormat";
+import { ToggleGroup } from "@calcom/ui";
+
+import { useTimePreferences } from "../lib";
+
+export const TimeFormatToggle = () => {
+ const timeFormat = useTimePreferences((state) => state.timeFormat);
+ const setTimeFormat = useTimePreferences((state) => state.setTimeFormat);
+ const { t } = useLocale();
+
+ return (
+ {
+ if (newFormat !== timeFormat) setTimeFormat(newFormat as TimeFormat);
+ }}
+ defaultValue={timeFormat}
+ value={timeFormat}
+ options={[
+ { value: TimeFormat.TWELVE_HOUR, label: t("12_hour_short") },
+ { value: TimeFormat.TWENTY_FOUR_HOUR, label: t("24_hour_short") },
+ ]}
+ />
+ );
+};
diff --git a/packages/features/bookings/components/event-meta/Details.tsx b/packages/features/bookings/components/event-meta/Details.tsx
new file mode 100644
index 0000000000..f1e68b0d1c
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Details.tsx
@@ -0,0 +1,168 @@
+import { Fragment } from "react";
+import React from "react";
+
+import classNames from "@calcom/lib/classNames";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Info, Clock, CheckSquare, RefreshCcw, CreditCard } from "@calcom/ui/components/icon";
+
+import type { PublicEvent } from "../../types";
+import { EventDetailBlocks } from "../../types";
+import { EventDuration } from "./Duration";
+import { EventLocations } from "./Locations";
+import { EventOccurences } from "./Occurences";
+import { EventPrice } from "./Price";
+
+type EventDetailsPropsBase = {
+ event: PublicEvent;
+ className?: string;
+};
+
+type EventDetailDefaultBlock = {
+ blocks?: EventDetailBlocks[];
+};
+
+// Rendering a custom block requires passing a name prop,
+// which is used as a key for the block.
+type EventDetailCustomBlock = {
+ blocks?: React.FC[];
+ name: string;
+};
+
+type EventDetailsProps = EventDetailsPropsBase & (EventDetailDefaultBlock | EventDetailCustomBlock);
+
+interface EventMetaProps {
+ icon: React.FC<{ className: string }> | string;
+ children: React.ReactNode;
+ // Emphasises the text in the block. For now only
+ // applying in dark mode.
+ highlight?: boolean;
+ contentClassName?: string;
+ className?: string;
+}
+
+/**
+ * Default order in which the event details will be rendered.
+ */
+const defaultEventDetailsBlocks = [
+ EventDetailBlocks.DESCRIPTION,
+ EventDetailBlocks.REQUIRES_CONFIRMATION,
+ EventDetailBlocks.DURATION,
+ EventDetailBlocks.OCCURENCES,
+ EventDetailBlocks.LOCATION,
+ EventDetailBlocks.PRICE,
+];
+
+/**
+ * Helper component that ensures the meta data of an event is
+ * rendered in a consistent way — adds an icon and children (text usually).
+ */
+export const EventMetaBlock = ({
+ icon: Icon,
+ children,
+ highlight,
+ contentClassName,
+ className,
+}: EventMetaProps) => {
+ if (!React.Children.count(children)) return null;
+
+ return (
+
+ {typeof Icon === "string" ? (
+
+ ) : (
+
+ )}
+
{children}
+
+ );
+};
+
+/**
+ * Component that renders event meta data in a structured way, with icons and labels.
+ * The component can be configured to show only specific blocks by overriding the
+ * `blocks` prop. The blocks prop takes in an array of block names, defined
+ * in the `EventDetailBlocks` enum. See the `defaultEventDetailsBlocks` const
+ * for the default order in which the blocks will be rendered.
+ *
+ * As part of the blocks array you can also decide to render a custom React Component,
+ * which will then also be rendered.
+ *
+ * Example:
+ * const MyCustomBlock = () => Something nice
;
+ *
+ */
+export const EventDetails = ({ event, blocks = defaultEventDetailsBlocks }: EventDetailsProps) => {
+ const { t } = useLocale();
+
+ return (
+ <>
+ {blocks.map((block) => {
+ if (typeof block === "function") {
+ return {block(event)} ;
+ }
+
+ switch (block) {
+ case EventDetailBlocks.DESCRIPTION:
+ if (!event.description) return null;
+ return (
+
+
+
+ );
+
+ case EventDetailBlocks.DURATION:
+ return (
+
+
+
+ );
+
+ case EventDetailBlocks.LOCATION:
+ if (!event?.locations?.length) return null;
+ return (
+
+
+
+ );
+
+ case EventDetailBlocks.REQUIRES_CONFIRMATION:
+ if (!event.requiresConfirmation) return null;
+
+ return (
+
+ {t("requires_confirmation")}
+
+ );
+
+ case EventDetailBlocks.OCCURENCES:
+ if (!event.requiresConfirmation || !event.recurringEvent) return null;
+
+ return (
+
+
+
+ );
+
+ case EventDetailBlocks.PRICE:
+ if (event.price === 0) return null;
+
+ return (
+
+
+
+ );
+ }
+ })}
+ >
+ );
+};
diff --git a/packages/features/bookings/components/event-meta/Duration.tsx b/packages/features/bookings/components/event-meta/Duration.tsx
new file mode 100644
index 0000000000..f3965a9b6f
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Duration.tsx
@@ -0,0 +1,37 @@
+import { useEffect } from "react";
+
+import { useBookerStore } from "@calcom/features/bookings/Booker/store";
+import classNames from "@calcom/lib/classNames";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Badge } from "@calcom/ui";
+
+import type { PublicEvent } from "../../types";
+
+export const EventDuration = ({ event }: { event: PublicEvent }) => {
+ const { t } = useLocale();
+ const [selectedDuration, setSelectedDuration] = useBookerStore((state) => [
+ state.selectedDuration,
+ state.setSelectedDuration,
+ ]);
+
+ // Sets initial value of selected duration to the default duration.
+ useEffect(() => {
+ // Only store event duration in url if event has multiple durations.
+ if (!selectedDuration && event.metadata?.multipleDuration) setSelectedDuration(event.length);
+ }, [selectedDuration, setSelectedDuration, event.length, event.metadata?.multipleDuration]);
+
+ if (!event?.metadata?.multipleDuration) return <>{t("multiple_duration_mins", { count: event.length })}>;
+
+ return (
+
+ {event.metadata.multipleDuration.map((duration) => (
+ setSelectedDuration(duration)}>{`${duration} ${t("minute_timeUnit")}`}
+ ))}
+
+ );
+};
diff --git a/packages/features/bookings/components/event-meta/EventMeta.stories.mdx b/packages/features/bookings/components/event-meta/EventMeta.stories.mdx
new file mode 100644
index 0000000000..d177723916
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/EventMeta.stories.mdx
@@ -0,0 +1,42 @@
+import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
+import { Examples, Example, Note, Title, VariantsTable, VariantColumn, RowTitles, CustomArgsTable} from '@calcom/storybook/components'
+import { Icon } from "@calcom/ui";
+
+import { EventDetails } from './Details';
+import { EventTitle } from './Title';
+import { EventMembers } from './Members';
+import { mockEvent } from './event.mock.ts';
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Quick catch-up
+
+
+
+
+
+
diff --git a/packages/features/bookings/components/event-meta/Locations.tsx b/packages/features/bookings/components/event-meta/Locations.tsx
new file mode 100644
index 0000000000..2c5db9177b
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Locations.tsx
@@ -0,0 +1,42 @@
+import { getEventLocationType } from "@calcom/app-store/locations";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Tooltip } from "@calcom/ui";
+import { MapPin } from "@calcom/ui/components/icon";
+
+import type { PublicEvent } from "../../types";
+import { EventMetaBlock } from "./Details";
+
+export const EventLocations = ({ event }: { event: PublicEvent }) => {
+ const { t } = useLocale();
+ const locations = event.locations;
+ if (!locations?.length) return null;
+
+ return (
+
+ {locations.length === 1 && (
+ {t(getEventLocationType(locations[0].type)?.label ?? "")}
+ )}
+ {locations.length > 1 && (
+
+
+ {t("select_on_next_step")}
+
+ {locations.map((location) => (
+
+ {t(getEventLocationType(location.type)?.label ?? "")}
+
+ ))}
+
+ >
+ }>
+ {t("num_locations", { num: locations.length })}
+
+
+ )}
+
+ );
+};
diff --git a/packages/features/bookings/components/event-meta/Members.tsx b/packages/features/bookings/components/event-meta/Members.tsx
new file mode 100644
index 0000000000..e416843322
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Members.tsx
@@ -0,0 +1,42 @@
+import { CAL_URL } from "@calcom/lib/constants";
+import { AvatarGroup } from "@calcom/ui";
+
+import type { PublicEvent } from "../../types";
+import { SchedulingType } from ".prisma/client";
+
+export interface EventMembersProps {
+ /**
+ * Used to determine whether all members should be shown or not.
+ * In case of Round Robin type, members aren't shown.
+ */
+ schedulingType: PublicEvent["schedulingType"];
+ users: PublicEvent["users"];
+ profile: PublicEvent["profile"];
+}
+
+export const EventMembers = ({ schedulingType, users, profile }: EventMembersProps) => {
+ const showMembers = schedulingType !== SchedulingType.ROUND_ROBIN;
+ const shownUsers = showMembers ? [...users, profile] : [profile];
+
+ const avatars = shownUsers
+ .map((user) => ({
+ title: `${user.name}`,
+ image: "image" in user ? `${user.image}` : `${CAL_URL}/${user.username}/avatar.png`,
+ alt: user.name || undefined,
+ href: user.username ? `${CAL_URL}/${user.username}` : undefined,
+ }))
+ .filter((item) => !!item.image)
+ .filter((item, index, self) => self.findIndex((t) => t.image === item.image) === index);
+
+ return (
+ <>
+
+
+ {users
+ .map((user) => user.name)
+ .filter((name) => name)
+ .join(", ")}
+
+ >
+ );
+};
diff --git a/packages/features/bookings/components/event-meta/Occurences.tsx b/packages/features/bookings/components/event-meta/Occurences.tsx
new file mode 100644
index 0000000000..f7b9b72c18
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Occurences.tsx
@@ -0,0 +1,42 @@
+import { useEffect } from "react";
+
+import { useBookerStore } from "@calcom/features/bookings/Booker/store";
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { getRecurringFreq } from "@calcom/lib/recurringStrings";
+import { Input } from "@calcom/ui";
+
+import type { PublicEvent } from "../../types";
+
+export const EventOccurences = ({ event }: { event: PublicEvent }) => {
+ const { t } = useLocale();
+ const [setRecurringEventCount, recurringEventCount] = useBookerStore((state) => [
+ state.setRecurringEventCount,
+ state.recurringEventCount,
+ ]);
+
+ // Set initial value in booker store.
+ useEffect(() => {
+ if (!event.recurringEvent?.count) return;
+ setRecurringEventCount(event.recurringEvent.count);
+ }, [setRecurringEventCount, event.recurringEvent]);
+
+ if (!event.recurringEvent) return null;
+
+ return (
+ <>
+ {getRecurringFreq({ t, recurringEvent: event.recurringEvent })}
+
+ {
+ setRecurringEventCount(parseInt(event?.target.value));
+ }}
+ />
+ {t("occurrence", {
+ count: recurringEventCount || event.recurringEvent.count,
+ })}
+ >
+ );
+};
diff --git a/packages/features/bookings/components/event-meta/Price.tsx b/packages/features/bookings/components/event-meta/Price.tsx
new file mode 100644
index 0000000000..792e88a444
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Price.tsx
@@ -0,0 +1,18 @@
+import getPaymentAppData from "@calcom/lib/getPaymentAppData";
+
+import type { PublicEvent } from "../../types";
+
+export const EventPrice = ({ event }: { event: PublicEvent }) => {
+ const stripeAppData = getPaymentAppData(event);
+
+ if (stripeAppData.price === 0) return null;
+
+ return (
+ <>
+ {Intl.NumberFormat("en", {
+ style: "currency",
+ currency: stripeAppData.currency.toUpperCase(),
+ }).format(stripeAppData.price / 100.0)}
+ >
+ );
+};
diff --git a/packages/features/bookings/components/event-meta/Skeleton.tsx b/packages/features/bookings/components/event-meta/Skeleton.tsx
new file mode 100644
index 0000000000..cc7f73d255
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Skeleton.tsx
@@ -0,0 +1,19 @@
+import classNames from "@calcom/lib/classNames";
+import { SkeletonText } from "@calcom/ui";
+
+export const EventMetaSkeleton = () => (
+
+
+
+
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+
+ 1 ? "w-24" : "w-32")} />
+
+ ))}
+
+
+);
diff --git a/packages/features/bookings/components/event-meta/Title.tsx b/packages/features/bookings/components/event-meta/Title.tsx
new file mode 100644
index 0000000000..a147b8cfc2
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/Title.tsx
@@ -0,0 +1,15 @@
+import classNames from "@calcom/lib/classNames";
+
+interface EventTitleProps {
+ children: React.ReactNode;
+ /**
+ * Option to override the default h1 tag.
+ */
+ as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span";
+ className?: string;
+}
+
+export const EventTitle = ({ children, as, className }: EventTitleProps) => {
+ const El = as || "h1";
+ return {children} ;
+};
diff --git a/packages/features/bookings/components/event-meta/event.mock.ts b/packages/features/bookings/components/event-meta/event.mock.ts
new file mode 100644
index 0000000000..1b9e0cad9d
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/event.mock.ts
@@ -0,0 +1,14 @@
+import { RouterOutputs } from "@calcom/trpc/react";
+
+export const mockEvent: RouterOutputs["viewer"]["public"]["event"] = {
+ id: 1,
+ title: "Quick check-in",
+ slug: "quick-check-in",
+ eventName: "Quick check-in",
+ description:
+ "Use this event for a quick 15 minute catchup. Visit this long url to test the component https://cal.com/averylongurlwithoutspacesthatshouldntbreaklayout",
+ users: [{ name: "Pro Example", username: "pro" }],
+ schedulingType: null,
+ length: 30,
+ locations: [{ type: "integrations:google:meet" }, { type: "integrations:zoom" }],
+};
diff --git a/packages/features/bookings/components/event-meta/index.ts b/packages/features/bookings/components/event-meta/index.ts
new file mode 100644
index 0000000000..ca610b80d5
--- /dev/null
+++ b/packages/features/bookings/components/event-meta/index.ts
@@ -0,0 +1,4 @@
+export { EventDetails, EventMetaBlock } from "./Details";
+export { EventTitle } from "./Title";
+export { EventMetaSkeleton } from "./Skeleton";
+export { EventMembers } from "./Members";
diff --git a/packages/features/bookings/index.ts b/packages/features/bookings/index.ts
new file mode 100644
index 0000000000..75d6c92b0e
--- /dev/null
+++ b/packages/features/bookings/index.ts
@@ -0,0 +1,8 @@
+export {
+ EventDetails,
+ EventMembers,
+ EventMetaBlock,
+ EventMetaSkeleton,
+ EventTitle,
+} from "./components/event-meta";
+export { AvailableTimes, AvailableTimesSkeleton } from "./components/AvailableTimes";
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
new file mode 100644
index 0000000000..9cf82e7b1f
--- /dev/null
+++ b/packages/features/bookings/lib/book-event-form/booking-to-mutation-input-mapper.tsx
@@ -0,0 +1,83 @@
+import { v4 as uuidv4 } from "uuid";
+
+import dayjs from "@calcom/dayjs";
+import { parseRecurringDates } from "@calcom/lib/parse-dates";
+
+import type { PublicEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../types";
+
+type BookingOptions = {
+ values: Record;
+ event: PublicEvent;
+ date: string;
+ // @NOTE: duration is not validated in this function
+ duration: number | undefined | null;
+ timeZone: string;
+ language: string;
+ rescheduleUid: string | undefined;
+ username: string;
+ metadata?: Record;
+};
+
+export const mapBookingToMutationInput = ({
+ values,
+ event,
+ date,
+ duration,
+ timeZone,
+ language,
+ rescheduleUid,
+ username,
+ metadata,
+}: BookingOptions): BookingCreateBody => {
+ return {
+ ...values,
+ user: username,
+ start: dayjs(date).format(),
+ end: dayjs(date)
+ // Defaults to the default event length in case no custom duration is set.
+ .add(duration || event.length, "minute")
+ .format(),
+ eventTypeId: event.id,
+ eventTypeSlug: event.slug,
+ timeZone: timeZone,
+ language: language,
+ rescheduleUid,
+ metadata: metadata || {},
+ hasHashedBookingLink: false,
+ // hasHashedBookingLink,
+ // hashedLink,
+ };
+};
+
+// This method is here to ensure that the types are correct (recurring count is required),
+// as well as generate a unique ID for the recurring bookings and turn one single booking
+// into an array of mutiple bookings based on the recurring count.
+// Other than that it forwards the mapping to mapBookingToMutationInput.
+export const mapRecurringBookingToMutationInput = (
+ booking: BookingOptions,
+ recurringCount: number
+): RecurringBookingCreateBody[] => {
+ const recurringEventId = uuidv4();
+ const [, recurringDates] = parseRecurringDates(
+ {
+ startDate: booking.date,
+ timeZone: booking.timeZone,
+ recurringEvent: booking.event.recurringEvent,
+ recurringCount,
+ withDefaultTimeFormat: true,
+ },
+ booking.language
+ );
+
+ const input = mapBookingToMutationInput(booking);
+
+ return recurringDates.map((recurringDate) => ({
+ ...input,
+ start: dayjs(recurringDate).format(),
+ end: dayjs(recurringDate)
+ .add(booking.duration || booking.event.length, "minute")
+ .format(),
+ recurringEventId,
+ recurringCount: recurringDates.length,
+ }));
+};
diff --git a/packages/features/bookings/lib/create-booking.ts b/packages/features/bookings/lib/create-booking.ts
new file mode 100644
index 0000000000..e7931723c3
--- /dev/null
+++ b/packages/features/bookings/lib/create-booking.ts
@@ -0,0 +1,8 @@
+import { post } from "@calcom/lib/fetch-wrapper";
+
+import type { BookingCreateBody, BookingResponse } from "../types";
+
+export const createBooking = async (data: BookingCreateBody) => {
+ const response = await post("/api/book/event", data);
+ return response;
+};
diff --git a/apps/web/lib/mutations/bookings/create-recurring-booking.ts b/packages/features/bookings/lib/create-recurring-booking.ts
similarity index 67%
rename from apps/web/lib/mutations/bookings/create-recurring-booking.ts
rename to packages/features/bookings/lib/create-recurring-booking.ts
index 8cec5b82b4..41e29c467f 100644
--- a/apps/web/lib/mutations/bookings/create-recurring-booking.ts
+++ b/packages/features/bookings/lib/create-recurring-booking.ts
@@ -1,18 +1,10 @@
-import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
+import * as fetch from "@calcom/lib/fetch-wrapper";
import type { AppsStatus } from "@calcom/types/Calendar";
-import * as fetch from "@lib/core/http/fetch-wrapper";
-import type { BookingResponse } from "@lib/types/booking";
+import type { RecurringBookingCreateBody, BookingResponse } from "../types";
-type ExtendedBookingCreateBody = BookingCreateBody & {
- noEmail?: boolean;
- recurringCount?: number;
- appsStatus?: AppsStatus[] | undefined;
- allRecurringDates?: string[];
- currentRecurringIndex?: number;
-};
-
-const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
+// @TODO: Didn't look at the contents of this function in order to not break old booking page.
+export const createRecurringBooking = async (data: RecurringBookingCreateBody[]) => {
const createdBookings: BookingResponse[] = [];
const allRecurringDates: string[] = data.map((booking) => booking.start);
let appsStatus: AppsStatus[] | undefined = undefined;
@@ -35,7 +27,7 @@ const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
appsStatus = Object.values(calcAppsStatus);
}
- const response = await fetch.post("/api/book/event", {
+ const response = await fetch.post("/api/book/event", {
...booking,
appsStatus,
allRecurringDates,
@@ -46,5 +38,3 @@ const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
}
return createdBookings;
};
-
-export default createRecurringBooking;
diff --git a/packages/features/bookings/lib/get-booking.ts b/packages/features/bookings/lib/get-booking.ts
new file mode 100644
index 0000000000..40ef06c938
--- /dev/null
+++ b/packages/features/bookings/lib/get-booking.ts
@@ -0,0 +1,163 @@
+import type { Prisma, PrismaClient } from "@prisma/client";
+import type { z } from "zod";
+
+import { bookingResponsesDbSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
+import slugify from "@calcom/lib/slugify";
+import prisma from "@calcom/prisma";
+
+type BookingSelect = {
+ description: true;
+ customInputs: true;
+ attendees: {
+ select: {
+ email: true;
+ name: true;
+ };
+ };
+ location: true;
+};
+
+// Backward Compatibility for booking created before we had managed booking questions
+function getResponsesFromOldBooking(
+ rawBooking: Prisma.BookingGetPayload<{
+ select: BookingSelect;
+ }>
+) {
+ const customInputs = rawBooking.customInputs || {};
+ const responses = Object.keys(customInputs).reduce((acc, label) => {
+ acc[slugify(label) as keyof typeof acc] = customInputs[label as keyof typeof customInputs];
+ return acc;
+ }, {});
+ return {
+ // It is possible to have no attendees in a booking when the booking is cancelled.
+ name: rawBooking.attendees[0]?.name || "Nameless",
+ email: rawBooking.attendees[0]?.email || "",
+ guests: rawBooking.attendees.slice(1).map((attendee) => {
+ return attendee.email;
+ }),
+ notes: rawBooking.description || "",
+ location: {
+ value: rawBooking.location || "",
+ optionValue: rawBooking.location || "",
+ },
+ ...responses,
+ };
+}
+
+async function getBooking(prisma: PrismaClient, uid: string) {
+ const rawBooking = await prisma.booking.findFirst({
+ where: {
+ uid,
+ },
+ select: {
+ id: true,
+ uid: true,
+ startTime: true,
+ description: true,
+ customInputs: true,
+ responses: true,
+ smsReminderNumber: true,
+ location: true,
+ attendees: {
+ select: {
+ email: true,
+ name: true,
+ bookingSeat: true,
+ },
+ },
+ user: {
+ select: {
+ id: true,
+ },
+ },
+ },
+ });
+
+ if (!rawBooking) {
+ return rawBooking;
+ }
+
+ const booking = getBookingWithResponses(rawBooking);
+
+ if (booking) {
+ // @NOTE: had to do this because Server side cant return [Object objects]
+ // probably fixable with json.stringify -> json.parse
+ booking["startTime"] = (booking?.startTime as Date)?.toISOString() as unknown as Date;
+ }
+
+ return booking;
+}
+
+export type GetBookingType = Prisma.PromiseReturnType;
+
+export const getBookingWithResponses = <
+ T extends Prisma.BookingGetPayload<{
+ select: BookingSelect & {
+ responses: true;
+ };
+ }>
+>(
+ booking: T
+) => {
+ return {
+ ...booking,
+ responses: bookingResponsesDbSchema.parse(booking.responses || getResponsesFromOldBooking(booking)),
+ } as Omit & { responses: z.infer };
+};
+
+export default getBooking;
+
+export const getBookingByUidOrRescheduleUid = async (uid: string) => {
+ let eventTypeId: number | null = null;
+ let rescheduleUid: string | null = null;
+ eventTypeId =
+ (
+ await prisma.booking.findFirst({
+ where: {
+ uid,
+ },
+ select: {
+ eventTypeId: true,
+ },
+ })
+ )?.eventTypeId || null;
+
+ // If no booking is found via the uid, it's probably a booking seat,
+ // which we query next.
+ let attendeeEmail: string | null = null;
+ if (!eventTypeId) {
+ const bookingSeat = await prisma.bookingSeat.findFirst({
+ where: {
+ referenceUid: uid,
+ },
+ select: {
+ id: true,
+ attendee: true,
+ booking: {
+ select: {
+ uid: true,
+ },
+ },
+ },
+ });
+ if (bookingSeat) {
+ rescheduleUid = bookingSeat.booking.uid;
+ attendeeEmail = bookingSeat.attendee.email;
+ }
+ }
+
+ // If we don't have a booking and no rescheduleUid, the ID is invalid,
+ // and we return null here.
+ if (!eventTypeId && !rescheduleUid) return null;
+
+ const booking = await getBooking(prisma, rescheduleUid || uid);
+
+ if (!booking) return null;
+
+ return {
+ ...booking,
+ attendees: rescheduleUid
+ ? booking.attendees.filter((attendee) => attendee.email === attendeeEmail)
+ : booking.attendees,
+ };
+};
diff --git a/packages/features/bookings/lib/index.ts b/packages/features/bookings/lib/index.ts
new file mode 100644
index 0000000000..a9a36c1264
--- /dev/null
+++ b/packages/features/bookings/lib/index.ts
@@ -0,0 +1,7 @@
+export { useTimePreferences, timePreferencesStore } from "./timePreferences";
+export {
+ mapBookingToMutationInput,
+ mapRecurringBookingToMutationInput,
+} from "./book-event-form/booking-to-mutation-input-mapper";
+export { createBooking } from "./create-booking";
+export { createRecurringBooking } from "./create-recurring-booking";
diff --git a/packages/features/bookings/lib/timePreferences.ts b/packages/features/bookings/lib/timePreferences.ts
new file mode 100644
index 0000000000..4e56f1bf16
--- /dev/null
+++ b/packages/features/bookings/lib/timePreferences.ts
@@ -0,0 +1,34 @@
+import { create } from "zustand";
+
+import dayjs from "@calcom/dayjs";
+import { TimeFormat, detectBrowserTimeFormat, setIs24hClockInLocalStorage } from "@calcom/lib/timeFormat";
+import { localStorage } from "@calcom/lib/webstorage";
+
+type TimePreferencesStore = {
+ timeFormat: TimeFormat.TWELVE_HOUR | TimeFormat.TWENTY_FOUR_HOUR;
+ setTimeFormat: (format: TimeFormat.TWELVE_HOUR | TimeFormat.TWENTY_FOUR_HOUR) => void;
+ timezone: string;
+ setTimezone: (timeZone: string) => void;
+};
+
+const timezoneLocalStorageKey = "timeOption.preferredTimeZone";
+
+/**
+ * This hook is NOT inside the user feature, since
+ * these settings only apply to the booker component. They will not reflect
+ * any changes made in the user settings.
+ */
+export const timePreferencesStore = create((set) => ({
+ timeFormat: detectBrowserTimeFormat,
+ setTimeFormat: (format: TimeFormat.TWELVE_HOUR | TimeFormat.TWENTY_FOUR_HOUR) => {
+ setIs24hClockInLocalStorage(format === TimeFormat.TWENTY_FOUR_HOUR);
+ set({ timeFormat: format });
+ },
+ timezone: localStorage.getItem(timezoneLocalStorageKey) || dayjs.tz.guess(),
+ setTimezone: (timezone: string) => {
+ localStorage.setItem(timezoneLocalStorageKey, timezone);
+ set({ timezone });
+ },
+}));
+
+export const useTimePreferences = timePreferencesStore;
diff --git a/packages/features/bookings/types.ts b/packages/features/bookings/types.ts
new file mode 100644
index 0000000000..6415612cd0
--- /dev/null
+++ b/packages/features/bookings/types.ts
@@ -0,0 +1,33 @@
+import type { ErrorOption, FieldPath } from "react-hook-form";
+
+import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
+import type { RouterOutputs } from "@calcom/trpc/react";
+import type { AppsStatus } from "@calcom/types/Calendar";
+
+export type PublicEvent = NonNullable;
+export type ValidationErrors = { key: FieldPath; error: ErrorOption }[];
+
+export enum EventDetailBlocks {
+ DESCRIPTION,
+ // Includes duration select when event has multiple durations.
+ DURATION,
+ LOCATION,
+ REQUIRES_CONFIRMATION,
+ // Includes input to select # of occurences.
+ OCCURENCES,
+ PRICE,
+}
+
+export type { BookingCreateBody };
+
+export type RecurringBookingCreateBody = BookingCreateBody & {
+ noEmail?: boolean;
+ recurringCount?: number;
+ appsStatus?: AppsStatus[] | undefined;
+ allRecurringDates?: string[];
+ currentRecurringIndex?: number;
+};
+
+export type BookingResponse = Awaited<
+ ReturnType
+>;
diff --git a/packages/features/eventtypes/components/CheckedUserSelect.tsx b/packages/features/eventtypes/components/CheckedUserSelect.tsx
index db3fcbfb59..d1c188f003 100644
--- a/packages/features/eventtypes/components/CheckedUserSelect.tsx
+++ b/packages/features/eventtypes/components/CheckedUserSelect.tsx
@@ -1,10 +1,9 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import type { Props } from "react-select";
-import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Avatar, EmptyScreen, Label, Select } from "@calcom/ui";
-import { FiUserPlus, FiX } from "@calcom/ui/components/icon";
+import { UserPlus, X } from "@calcom/ui/components/icon";
export type CheckedUserSelectOption = {
avatar: string;
@@ -48,14 +47,13 @@ export const CheckedUserSelect = ({
{value.map((option, index) => {
- const calLink = `${CAL_URL}/${option.value}`;
return (
{option.label}
- props.onChange(value.filter((item) => item.value !== option.value))}
className="my-auto ml-auto"
/>
@@ -68,7 +66,7 @@ export const CheckedUserSelect = ({
) : (
diff --git a/packages/features/eventtypes/lib/getPublicEvent.ts b/packages/features/eventtypes/lib/getPublicEvent.ts
new file mode 100644
index 0000000000..a29b4e41b1
--- /dev/null
+++ b/packages/features/eventtypes/lib/getPublicEvent.ts
@@ -0,0 +1,201 @@
+import type { User } from "@prisma/client";
+import { Prisma } from "@prisma/client";
+
+import type { LocationObject } from "@calcom/app-store/locations";
+import { privacyFilteredLocations } from "@calcom/app-store/locations";
+import { getAppFromSlug } from "@calcom/app-store/utils";
+import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
+import { isRecurringEvent, parseRecurringEvent } from "@calcom/lib";
+import { WEBAPP_URL } from "@calcom/lib/constants";
+import { getDefaultEvent } from "@calcom/lib/defaultEvents";
+import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
+import type { PrismaClient } from "@calcom/prisma/client";
+import {
+ EventTypeMetaDataSchema,
+ customInputSchema,
+ userMetadata as userMetadataSchema,
+} from "@calcom/prisma/zod-utils";
+
+const publicEventSelect = Prisma.validator
()({
+ id: true,
+ title: true,
+ description: true,
+ eventName: true,
+ slug: true,
+ schedulingType: true,
+ length: true,
+ locations: true,
+ customInputs: true,
+ disableGuests: true,
+ // @TODO: Could this contain sensitive data?
+ metadata: true,
+ requiresConfirmation: true,
+ recurringEvent: true,
+ price: true,
+ currency: true,
+ seatsPerTimeSlot: true,
+ bookingFields: true,
+ team: true,
+ workflows: {
+ include: {
+ workflow: {
+ include: {
+ steps: true,
+ },
+ },
+ },
+ },
+ hosts: {
+ select: {
+ user: {
+ select: {
+ username: true,
+ name: true,
+ weekStart: true,
+ brandColor: true,
+ darkBrandColor: true,
+ },
+ },
+ },
+ },
+ owner: true,
+});
+
+export const getPublicEvent = async (username: string, eventSlug: string, prisma: PrismaClient) => {
+ const usernameList = username.split("+");
+
+ // In case of dynamic group event, we fetch user's data and use the default event.
+ if (usernameList.length > 1) {
+ const users = await prisma.user.findMany({
+ where: {
+ username: {
+ in: usernameList,
+ },
+ },
+ select: {
+ username: true,
+ name: true,
+ weekStart: true,
+ metadata: true,
+ brandColor: true,
+ darkBrandColor: true,
+ },
+ });
+
+ const defaultEvent = getDefaultEvent(eventSlug);
+ let locations = defaultEvent.locations ? (defaultEvent.locations as LocationObject[]) : [];
+
+ // Get the prefered location type from the first user
+ const firstUsersMetadata = userMetadataSchema.parse(users[0].metadata || {});
+ const preferedLocationType = firstUsersMetadata?.defaultConferencingApp;
+
+ if (preferedLocationType?.appSlug) {
+ const foundApp = getAppFromSlug(preferedLocationType.appSlug);
+ const appType = foundApp?.appData?.location?.type;
+ if (appType) {
+ // Replace the location with the prefered location type
+ // This will still be default to daily if the app is not found
+ locations = [{ type: appType, link: preferedLocationType.appLink }] as LocationObject[];
+ }
+ }
+
+ return {
+ ...defaultEvent,
+ bookingFields: getBookingFieldsWithSystemFields(defaultEvent),
+ // Clears meta data since we don't want to send this in the public api.
+ users: users.map((user) => ({ ...user, metadata: undefined })),
+ locations: privacyFilteredLocations(locations),
+ profile: {
+ username: users[0].username,
+ name: users[0].name,
+ weekStart: users[0].weekStart,
+ image: `${WEBAPP_URL}/${users[0].username}/avatar.png`,
+ brandColor: users[0].brandColor,
+ darkBrandColor: users[0].darkBrandColor,
+ },
+ };
+ }
+
+ // In case it's not a group event, it's either a single user or a team, and we query that data.
+ const event = await prisma.eventType.findFirst({
+ where: {
+ slug: eventSlug,
+ OR: [
+ {
+ users: {
+ some: {
+ username,
+ },
+ },
+ },
+ {
+ team: {
+ slug: username,
+ },
+ },
+ ],
+ },
+ select: publicEventSelect,
+ });
+
+ if (!event) return null;
+
+ return {
+ ...event,
+ description: markdownToSafeHTML(event.description),
+ metadata: EventTypeMetaDataSchema.parse(event.metadata || {}),
+ customInputs: customInputSchema.array().parse(event.customInputs || []),
+ locations: privacyFilteredLocations((event.locations || []) as LocationObject[]),
+ bookingFields: getBookingFieldsWithSystemFields(event),
+ recurringEvent: isRecurringEvent(event.recurringEvent) ? parseRecurringEvent(event.recurringEvent) : null,
+ // Sets user data on profile object for easier access
+ profile: getProfileFromEvent(event),
+ users: getUsersFromEvent(event),
+ };
+};
+
+const eventData = Prisma.validator()({
+ select: publicEventSelect,
+});
+
+type Event = Prisma.EventTypeGetPayload;
+
+function getProfileFromEvent(event: Event) {
+ const { team, hosts, owner } = event;
+ const profile = team || hosts?.[0]?.user || owner;
+ if (!profile) throw new Error("Event has no owner");
+
+ const username = "username" in profile ? profile.username : team?.slug;
+ if (!username) throw new Error("Event has no username/team slug");
+ const weekStart = hosts?.[0]?.user?.weekStart || owner?.weekStart || "Monday";
+ const basePath = team ? `/team/${username}` : `/${username}`;
+
+ return {
+ username,
+ name: profile.name,
+ weekStart,
+ image: `${WEBAPP_URL}${basePath}/avatar.png`,
+ brandColor: profile.brandColor,
+ darkBrandColor: profile.darkBrandColor,
+ };
+}
+
+function getUsersFromEvent(event: Event) {
+ const { team, hosts, owner } = event;
+ if (team) {
+ return (hosts || []).map(mapHostsToUsers);
+ }
+
+ if (!owner) throw new Error("Event has no owner");
+
+ const { username, name, weekStart } = owner;
+ return [{ username, name, weekStart }];
+}
+
+function mapHostsToUsers(host: { user: Pick }) {
+ return {
+ username: host.user.username,
+ name: host.user.name,
+ weekStart: host.user.weekStart,
+ };
+}
diff --git a/packages/features/package.json b/packages/features/package.json
index f1c05ccb33..7582de6295 100644
--- a/packages/features/package.json
+++ b/packages/features/package.json
@@ -12,7 +12,13 @@
"@calcom/ui": "*",
"@lexical/react": "^0.5.0",
"dompurify": "^2.4.1",
+ "framer-motion": "^10.12.3",
"lexical": "^0.5.0",
- "zustand": "^4.1.4"
+ "react-sticky-box": "^2.0.4",
+ "zustand": "^4.3.2"
+ },
+ "devDependencies": {
+ "@testing-library/react-hooks": "^8.0.1",
+ "mockdate": "^3.0.5"
}
}
diff --git a/packages/features/schedules/index.ts b/packages/features/schedules/index.ts
index 40b494c5f8..fe06d0e457 100644
--- a/packages/features/schedules/index.ts
+++ b/packages/features/schedules/index.ts
@@ -1 +1,3 @@
export * from "./components";
+export type { Slots } from "./lib/use-schedule";
+export { useSchedule, useSlotsForDate, useNonEmptyScheduleDays } from "./lib/use-schedule";
diff --git a/packages/features/schedules/lib/use-schedule/index.ts b/packages/features/schedules/lib/use-schedule/index.ts
new file mode 100644
index 0000000000..ce81319e9c
--- /dev/null
+++ b/packages/features/schedules/lib/use-schedule/index.ts
@@ -0,0 +1,4 @@
+export { useSchedule } from "./useSchedule";
+export { useSlotsForDate } from "./useSlotsForDate";
+export { useNonEmptyScheduleDays } from "./useNonEmptyScheduleDays";
+export type { Slots } from "./types";
diff --git a/packages/features/schedules/lib/use-schedule/types.ts b/packages/features/schedules/lib/use-schedule/types.ts
new file mode 100644
index 0000000000..72ba9df508
--- /dev/null
+++ b/packages/features/schedules/lib/use-schedule/types.ts
@@ -0,0 +1,3 @@
+import { RouterOutputs } from "@calcom/trpc/react";
+
+export type Slots = RouterOutputs["viewer"]["public"]["slots"]["getSchedule"]["slots"];
diff --git a/packages/features/schedules/lib/use-schedule/useNonEmptyScheduleDays.ts b/packages/features/schedules/lib/use-schedule/useNonEmptyScheduleDays.ts
new file mode 100644
index 0000000000..eca607c322
--- /dev/null
+++ b/packages/features/schedules/lib/use-schedule/useNonEmptyScheduleDays.ts
@@ -0,0 +1,14 @@
+import { useMemo } from "react";
+
+import type { Slots } from "../use-schedule";
+
+export const getNonEmptyScheduleDays = (slots?: Slots) => {
+ if (typeof slots === "undefined") return [];
+ return Object.keys(slots).filter((day) => slots[day].length > 0);
+};
+
+export const useNonEmptyScheduleDays = (slots?: Slots) => {
+ const days = useMemo(() => getNonEmptyScheduleDays(slots), [slots]);
+
+ return days;
+};
diff --git a/packages/features/schedules/lib/use-schedule/useSchedule.ts b/packages/features/schedules/lib/use-schedule/useSchedule.ts
new file mode 100644
index 0000000000..40e1ad2028
--- /dev/null
+++ b/packages/features/schedules/lib/use-schedule/useSchedule.ts
@@ -0,0 +1,49 @@
+import dayjs from "@calcom/dayjs";
+import { trpc } from "@calcom/trpc/react";
+
+type UseScheduleWithCacheArgs = {
+ username?: string | null;
+ eventSlug?: string | null;
+ eventId?: number | null;
+ month?: string | null;
+ timezone?: string | null;
+ prefetchNextMonth?: boolean;
+};
+
+export const useSchedule = ({
+ month,
+ timezone,
+ username,
+ eventSlug,
+ eventId,
+ prefetchNextMonth,
+}: UseScheduleWithCacheArgs) => {
+ const monthDayjs = month ? dayjs(month) : dayjs();
+ const nextMonthDayjs = monthDayjs.add(1, "month");
+
+ // Why the non-null assertions? All of these arguments are checked in the enabled condition,
+ // and the query will not run if they are null. However, the check in `enabled` does
+ // no satisfy typscript.
+ return trpc.viewer.public.slots.getSchedule.useQuery(
+ {
+ usernameList: username && username.indexOf("+") > -1 ? username.split("+") : [username!],
+ eventTypeSlug: eventSlug!,
+ // @TODO: Old code fetched 2 days ago if we were fetching the current month.
+ // Do we want / need to keep that behavior?
+ startTime: monthDayjs.startOf("month").toISOString(),
+ // if `prefetchNextMonth` is true, two months are fetched at once.
+ endTime: (prefetchNextMonth ? nextMonthDayjs : monthDayjs).endOf("month").toISOString(),
+ timeZone: timezone!,
+ eventTypeId: eventId!,
+ },
+ {
+ refetchOnWindowFocus: false,
+ enabled:
+ Boolean(username) &&
+ Boolean(eventSlug) &&
+ (Boolean(eventId) || eventId === 0) &&
+ Boolean(month) &&
+ Boolean(timezone),
+ }
+ );
+};
diff --git a/packages/features/schedules/lib/use-schedule/useSlotsForDate.ts b/packages/features/schedules/lib/use-schedule/useSlotsForDate.ts
new file mode 100644
index 0000000000..b74ede7ee6
--- /dev/null
+++ b/packages/features/schedules/lib/use-schedule/useSlotsForDate.ts
@@ -0,0 +1,31 @@
+import { useMemo } from "react";
+
+import type { Slots } from "./types";
+
+/**
+ * Get's slots for a specific date from the schedul cache.
+ * @param date Format YYYY-MM-DD
+ * @param scheduleCache Instance of useScheduleWithCache
+ */
+export const useSlotsForDate = (date: string | null, slots?: Slots) => {
+ const slotsForDate = useMemo(() => {
+ if (!date || typeof slots === "undefined") return [];
+ return slots[date] || [];
+ }, [date, slots]);
+
+ return slotsForDate;
+};
+
+export const useSlotsForMultipleDates = (dates: (string | null)[], slots?: Slots) => {
+ const slotsForDates = useMemo(() => {
+ if (typeof slots === "undefined") return [];
+ return dates
+ .filter((date) => date !== null)
+ .map((date) => ({
+ slots: slots[`${date}`] || [],
+ date,
+ }));
+ }, [dates, slots]);
+
+ return slotsForDates;
+};
diff --git a/packages/features/tsconfig.json b/packages/features/tsconfig.json
index b8fb2a6430..baae362e9a 100644
--- a/packages/features/tsconfig.json
+++ b/packages/features/tsconfig.json
@@ -5,7 +5,8 @@
"paths": {
"~/*": ["/*"]
},
- "resolveJsonModule": true
+ "resolveJsonModule": true,
+ "esModuleInterop": true
},
"include": [".", "../types/next-auth.d.ts"],
"exclude": ["dist", "build", "node_modules"]
diff --git a/packages/lib/array.ts b/packages/lib/array.ts
new file mode 100644
index 0000000000..03d7c22932
--- /dev/null
+++ b/packages/lib/array.ts
@@ -0,0 +1 @@
+export const notUndefined = (val: T | undefined): val is T => Boolean(val);
diff --git a/packages/lib/date-fns/index.ts b/packages/lib/date-fns/index.ts
index a6e12f1100..9f92f7b7dc 100644
--- a/packages/lib/date-fns/index.ts
+++ b/packages/lib/date-fns/index.ts
@@ -130,6 +130,21 @@ export const isNextDayInTimezone = (time: string, timezoneA: string, timezoneB:
return hoursTimezoneBIsEarlier && timezoneBIsLaterTimezone;
};
+const weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] as const;
+type WeekDays = (typeof weekDays)[number];
+type WeekDayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
+
+/**
+ * Turns weekday string (eg "Monday") into a number (eg 1).
+ * Also accepts a number as parameter (and straight returns that), and accepts
+ * undefined as a parameter; returns 0 in that case.
+ */
+export const weekdayToWeekIndex = (weekday: WeekDays | string | number | undefined) => {
+ if (typeof weekday === "undefined") return 0;
+ if (typeof weekday === "number") return weekday >= 0 && weekday >= 6 ? (weekday as WeekDayIndex) : 0;
+ return (weekDays.indexOf(weekday as WeekDays) as WeekDayIndex) || 0;
+};
+
/**
* Dayjs does not expose the timeZone value publicly through .get("timeZone")
* instead, we as devs are required to somewhat hack our way to get the
diff --git a/apps/web/lib/core/http/fetch-wrapper.ts b/packages/lib/fetch-wrapper.ts
similarity index 97%
rename from apps/web/lib/core/http/fetch-wrapper.ts
rename to packages/lib/fetch-wrapper.ts
index 4d5d6365e8..9c7dceb358 100644
--- a/apps/web/lib/core/http/fetch-wrapper.ts
+++ b/packages/lib/fetch-wrapper.ts
@@ -1,4 +1,4 @@
-import { HttpError } from "@lib/core/http/error";
+import { HttpError } from "./http-error";
async function http(path: string, config: RequestInit): Promise {
const request = new Request(path, config);
diff --git a/apps/web/lib/parseDate.ts b/packages/lib/parse-dates.ts
similarity index 62%
rename from apps/web/lib/parseDate.ts
rename to packages/lib/parse-dates.ts
index b262b5569e..d61d2d59d5 100644
--- a/apps/web/lib/parseDate.ts
+++ b/packages/lib/parse-dates.ts
@@ -1,24 +1,28 @@
-import type { I18n } from "next-i18next";
import { RRule } from "rrule";
import type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
-import type { TimeFormat } from "@calcom/lib/timeFormat";
-import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
+import { detectBrowserTimeFormat, TimeFormat } from "@calcom/lib/timeFormat";
import type { RecurringEvent } from "@calcom/types/Calendar";
-import { parseZone } from "./parseZone";
+import { parseZone } from "./parse-zone";
-const processDate = (date: string | null | Dayjs, i18n: I18n, selectedTimeFormat?: TimeFormat) => {
+type ExtraOptions = { withDefaultTimeFormat?: boolean; selectedTimeFormat?: TimeFormat };
+
+const processDate = (date: string | null | Dayjs, language: string, options?: ExtraOptions) => {
const parsedZone = parseZone(date);
if (!parsedZone?.isValid()) return "Invalid date";
- const formattedTime = parsedZone?.format(selectedTimeFormat || detectBrowserTimeFormat);
- return formattedTime + ", " + dayjs(date).toDate().toLocaleString(i18n.language, { dateStyle: "full" });
+ const formattedTime = parsedZone?.format(
+ options?.withDefaultTimeFormat
+ ? TimeFormat.TWELVE_HOUR
+ : options?.selectedTimeFormat || detectBrowserTimeFormat
+ );
+ return formattedTime + ", " + dayjs(date).toDate().toLocaleString(language, { dateStyle: "full" });
};
-export const parseDate = (date: string | null | Dayjs, i18n: I18n, selectedTimeFormat?: TimeFormat) => {
+export const parseDate = (date: string | null | Dayjs, language: string, options?: ExtraOptions) => {
if (!date) return ["No date"];
- return processDate(date, i18n, selectedTimeFormat);
+ return processDate(date, language, options);
};
export const parseRecurringDates = (
@@ -28,14 +32,16 @@ export const parseRecurringDates = (
recurringEvent,
recurringCount,
selectedTimeFormat,
+ withDefaultTimeFormat,
}: {
startDate: string | null | Dayjs;
timeZone?: string;
recurringEvent: RecurringEvent | null;
recurringCount: number;
selectedTimeFormat?: TimeFormat;
+ withDefaultTimeFormat?: boolean;
},
- i18n: I18n
+ language: string
): [string[], Date[]] => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { count, ...restRecurringEvent } = recurringEvent || {};
@@ -53,7 +59,7 @@ export const parseRecurringDates = (
});
const dateStrings = times.map((t) => {
// finally; show in local timeZone again
- return processDate(t.tz(timeZone), i18n, selectedTimeFormat);
+ return processDate(t.tz(timeZone), language, { selectedTimeFormat, withDefaultTimeFormat });
});
return [dateStrings, times.map((t) => t.toDate())];
diff --git a/apps/web/lib/parseZone.ts b/packages/lib/parse-zone.ts
similarity index 100%
rename from apps/web/lib/parseZone.ts
rename to packages/lib/parse-zone.ts
diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx
index b2468cc89a..8ac94e6b4f 100644
--- a/packages/trpc/server/routers/viewer.tsx
+++ b/packages/trpc/server/routers/viewer.tsx
@@ -25,6 +25,7 @@ import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { samlTenantProduct } from "@calcom/features/ee/sso/lib/saml";
import { userAdminRouter } from "@calcom/features/ee/users/server/trpc-router";
+import { getPublicEvent } from "@calcom/features/eventtypes/lib/getPublicEvent";
import { featureFlagRouter } from "@calcom/features/flags/server/router";
import { insightsRouter } from "@calcom/features/insights/server/trpc-router";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
@@ -160,6 +161,17 @@ const publicViewerRouter = router({
}
}),
slots: slotsRouter,
+ event: publicProcedure
+ .input(
+ z.object({
+ username: z.string(),
+ eventSlug: z.string(),
+ })
+ )
+ .query(async ({ ctx, input }) => {
+ const event = await getPublicEvent(input.username, input.eventSlug, ctx.prisma);
+ return event;
+ }),
cityTimezones: publicProcedure.query(async () => {
/**
* Lazy loads third party dependency to avoid loading 1.5Mb for ALL tRPC procedures.
diff --git a/packages/ui/components/badge/Badge.tsx b/packages/ui/components/badge/Badge.tsx
index cdf2b89adb..f90331dcc9 100644
--- a/packages/ui/components/badge/Badge.tsx
+++ b/packages/ui/components/badge/Badge.tsx
@@ -1,6 +1,6 @@
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
-import type { ComponentProps, ReactNode } from "react";
+import React from "react";
import { GoPrimitiveDot } from "react-icons/go";
import classNames from "@calcom/lib/classNames";
@@ -41,19 +41,40 @@ type IconOrDot =
}
| { startIcon?: unknown; withDot?: boolean };
-export type BadgeProps = InferredBadgeStyles &
- ComponentProps<"div"> & { children: ReactNode; rounded?: boolean } & IconOrDot;
+export type BadgeBaseProps = InferredBadgeStyles & {
+ children: React.ReactNode;
+ rounded?: boolean;
+} & IconOrDot;
+
+export type BadgeProps =
+ /**
+ * This union type helps TypeScript understand that there's two options for this component:
+ * Either it's a div element on which the onClick prop is not allowed, or it's a button element
+ * on which the onClick prop is required. This is because the onClick prop is used to determine
+ * whether the component should be a button or a div.
+ */
+ | (BadgeBaseProps & Omit, "onClick"> & { onClick?: never })
+ | (BadgeBaseProps & Omit, "onClick"> & { onClick: () => void });
export const Badge = function Badge(props: BadgeProps) {
const { variant, className, size, startIcon, withDot, children, rounded, ...passThroughProps } = props;
+ const isButton = "onClick" in passThroughProps && passThroughProps.onClick !== undefined;
const StartIcon = startIcon ? (startIcon as SVGComponent) : undefined;
- return (
-
+ const classes = classNames(
+ badgeStyles({ variant, size }),
+ rounded && "h-5 w-5 rounded-full p-0",
+ className
+ );
+
+ const Children = () => (
+ <>
{withDot ?
: null}
{StartIcon ?
: null}
-
{children}
-
+ {children}
+ >
);
+
+ const Wrapper = isButton ? "button" : "div";
+
+ return React.createElement(Wrapper, { ...passThroughProps, className: classes }, );
};
diff --git a/packages/ui/components/form/index.ts b/packages/ui/components/form/index.ts
index cab81972dc..c751166c7d 100644
--- a/packages/ui/components/form/index.ts
+++ b/packages/ui/components/form/index.ts
@@ -22,7 +22,7 @@ export { Select, SelectField, SelectWithValidation, getReactSelectProps } from "
export { TimezoneSelect } from "./timezone-select";
export type { ITimezone, ITimezoneOption } from "./timezone-select";
export { DateRangePickerLazy as DateRangePicker } from "./date-range-picker";
-export { BooleanToggleGroup, BooleanToggleGroupField, ToggleGroup, ToggleGroupItem } from "./toggleGroup";
+export { BooleanToggleGroup, BooleanToggleGroupField, ToggleGroup } from "./toggleGroup";
export { DatePicker } from "./datepicker";
export { FormStep, Steps, Stepper } from "./step";
export { WizardForm } from "./wizard";
diff --git a/packages/ui/components/form/inputs/Input.tsx b/packages/ui/components/form/inputs/Input.tsx
index c8d891d14d..0e188cb506 100644
--- a/packages/ui/components/form/inputs/Input.tsx
+++ b/packages/ui/components/form/inputs/Input.tsx
@@ -12,10 +12,10 @@ import { Eye, EyeOff, X } from "../../icon";
import { HintsOrErrors } from "./HintOrErrors";
import { Label } from "./Label";
-type InputProps = JSX.IntrinsicElements["input"] & { isFullWidth?: boolean };
+type InputProps = JSX.IntrinsicElements["input"] & { isFullWidth?: boolean; isStandaloneField?: boolean };
export const Input = forwardRef(function Input(
- { isFullWidth = true, ...props },
+ { isFullWidth = true, isStandaloneField = true, ...props },
ref
) {
return (
@@ -140,6 +140,7 @@ export const InputField = forwardRef(function
type={type}
placeholder={placeholder}
isFullWidth={inputIsFullWidth}
+ isStandaloneField={false}
className={classNames(
className,
"disabled:bg-muted disabled:hover:border-subtle disabled:cursor-not-allowed",
@@ -162,10 +163,7 @@ export const InputField = forwardRef(function
{addOnSuffix && (
+ className={classNames("ltr:rounded-r-md rtl:rounded-l-md", addOnClassname)}>
{addOnSuffix}
)}
@@ -231,10 +229,7 @@ export const PasswordField = forwardRef(funct
addOnFilled={false}
addOnSuffix={
- toggleIsPasswordVisible()}>
+ toggleIsPasswordVisible()}>
{isPasswordVisible ? (
) : (
diff --git a/packages/ui/components/form/timezone-select/TimezoneSelect.tsx b/packages/ui/components/form/timezone-select/TimezoneSelect.tsx
index 129260b1e2..5977a7c6bc 100644
--- a/packages/ui/components/form/timezone-select/TimezoneSelect.tsx
+++ b/packages/ui/components/form/timezone-select/TimezoneSelect.tsx
@@ -15,6 +15,7 @@ export interface ICity {
export function TimezoneSelect({
className,
+ classNames: timezoneClassNames,
components,
variant = "default",
...props
@@ -49,13 +50,15 @@ export function TimezoneSelect({
formatOptionLabel={(option) => {(option as ITimezoneOption).value}
}
getOptionLabel={(option) => handleOptionLabel(option as ITimezoneOption, cities)}
classNames={{
- input: () => classNames("text-emphasis", props.classNames?.input),
+ ...timezoneClassNames,
+ input: (state) =>
+ classNames("text-emphasis", timezoneClassNames?.input && timezoneClassNames.input(state)),
option: (state) =>
classNames(
"bg-default flex cursor-pointer justify-between py-2.5 px-3 rounded-none text-default ",
state.isFocused && "bg-subtle",
state.isSelected && "bg-emphasis text-default",
- props.classNames?.option
+ timezoneClassNames?.option && timezoneClassNames.option(state)
),
placeholder: (state) => classNames("text-muted", state.isFocused && "hidden"),
dropdownIndicator: () => "text-default",
@@ -64,33 +67,44 @@ export function TimezoneSelect({
variant === "default"
? "px-3 py-2 bg-default border-default !min-h-9 text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:ring-2 focus-within:ring-emphasis hover:border-emphasis rounded-md border gap-1"
: "text-sm gap-1",
- props.classNames?.control
+ timezoneClassNames?.control && timezoneClassNames.control(state)
),
- singleValue: () => classNames("text-emphasis placeholder:text-muted", props.classNames?.singleValue),
- valueContainer: () =>
- classNames("text-emphasis placeholder:text-muted flex gap-1", props.classNames?.valueContainer),
- multiValue: () =>
+ singleValue: (state) =>
+ classNames(
+ "text-emphasis placeholder:text-muted",
+ timezoneClassNames?.singleValue && timezoneClassNames.singleValue(state)
+ ),
+ valueContainer: (state) =>
+ classNames(
+ "text-emphasis placeholder:text-muted flex gap-1",
+ timezoneClassNames?.valueContainer && timezoneClassNames.valueContainer(state)
+ ),
+ multiValue: (state) =>
classNames(
"bg-subtle text-default rounded-md py-1.5 px-2 flex items-center text-sm leading-none",
- props.classNames?.multiValue
+ timezoneClassNames?.multiValue && timezoneClassNames.multiValue(state)
),
- menu: () =>
+ menu: (state) =>
classNames(
"rounded-md bg-default text-sm leading-4 text-default mt-1 border border-subtle",
- props.classNames?.menu
+ timezoneClassNames?.menu && timezoneClassNames.menu(state)
),
groupHeading: () => "leading-none text-xs uppercase text-default pl-2.5 pt-4 pb-2",
- menuList: () => classNames("scroll-bar scrollbar-track-w-20 rounded-md", props.classNames?.menuList),
+ menuList: (state) =>
+ classNames(
+ "scroll-bar scrollbar-track-w-20 rounded-md",
+ timezoneClassNames?.menuList && timezoneClassNames.menuList(state)
+ ),
indicatorsContainer: (state) =>
classNames(
state.selectProps.menuIsOpen
? state.isMulti
? "[&>*:last-child]:rotate-180 [&>*:last-child]:transition-transform"
: "rotate-180 transition-transform"
- : "text-default" // Woo it adds another SVG here on multi for some reason
+ : "text-default", // Woo it adds another SVG here on multi for some reason
+ timezoneClassNames?.indicatorsContainer && timezoneClassNames.indicatorsContainer(state)
),
multiValueRemove: () => "text-default py-auto ml-2",
- ...props.classNames,
}}
/>
);
diff --git a/packages/ui/components/form/toggleGroup/ToggleGroup.tsx b/packages/ui/components/form/toggleGroup/ToggleGroup.tsx
index cc942b987f..dfe1584f82 100644
--- a/packages/ui/components/form/toggleGroup/ToggleGroup.tsx
+++ b/packages/ui/components/form/toggleGroup/ToggleGroup.tsx
@@ -1,15 +1,32 @@
import * as RadixToggleGroup from "@radix-ui/react-toggle-group";
+import type { ReactNode } from "react";
import { useEffect, useState } from "react";
import { classNames } from "@calcom/lib";
-
-export const ToggleGroupItem = () => hi
;
+import { Tooltip } from "@calcom/ui";
interface ToggleGroupProps extends Omit {
- options: { value: string; label: string; disabled?: boolean }[];
+ options: { value: string; label: string | ReactNode; disabled?: boolean; tooltip?: string }[];
isFullWidth?: boolean;
}
+const OptionalTooltipWrapper = ({
+ children,
+ tooltipText,
+}: {
+ children: ReactNode;
+ tooltipText?: ReactNode;
+}) => {
+ if (tooltipText) {
+ return (
+
+ {children}
+
+ );
+ }
+ return <>{children}>;
+};
+
export const ToggleGroup = ({ options, onValueChange, isFullWidth, ...props }: ToggleGroupProps) => {
const [value, setValue] = useState(props.defaultValue);
const [activeToggleElement, setActiveToggleElement] = useState(null);
@@ -36,25 +53,26 @@ export const ToggleGroup = ({ options, onValueChange, isFullWidth, ...props }: T
style={{ left: activeToggleElement?.offsetLeft, width: activeToggleElement?.offsetWidth }}
/>
{options.map((option) => (
- {
- if (node && value === option.value) {
- setActiveToggleElement(node);
- }
- return node;
- }}>
- {option.label}
-
+
+ {
+ if (node && value === option.value && activeToggleElement !== node) {
+ setActiveToggleElement(node);
+ }
+ return node;
+ }}>
+ {option.label}
+
+
))}
>
diff --git a/packages/ui/components/form/toggleGroup/index.ts b/packages/ui/components/form/toggleGroup/index.ts
index 582aaebb30..a111fc9c00 100644
--- a/packages/ui/components/form/toggleGroup/index.ts
+++ b/packages/ui/components/form/toggleGroup/index.ts
@@ -1,2 +1,2 @@
-export { ToggleGroup, ToggleGroupItem } from "./ToggleGroup";
+export { ToggleGroup } from "./ToggleGroup";
export { BooleanToggleGroup, BooleanToggleGroupField } from "./BooleanToggleGroup";
diff --git a/packages/ui/components/tooltip/Tooltip.tsx b/packages/ui/components/tooltip/Tooltip.tsx
index 6fa833f68d..80aba46461 100644
--- a/packages/ui/components/tooltip/Tooltip.tsx
+++ b/packages/ui/components/tooltip/Tooltip.tsx
@@ -1,7 +1,7 @@
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import React from "react";
-import { classNames } from "@calcom/lib";
+import classNames from "@calcom/lib/classNames";
export function Tooltip({
children,
@@ -9,19 +9,21 @@ export function Tooltip({
open,
defaultOpen,
onOpenChange,
+ delayDuration,
side = "top",
...props
}: {
children: React.ReactNode;
content: React.ReactNode;
+ delayDuration?: number;
open?: boolean;
defaultOpen?: boolean;
side?: "top" | "right" | "bottom" | "left";
onOpenChange?: (open: boolean) => void;
-}) {
+} & TooltipPrimitive.TooltipContentProps) {
return (
@@ -31,7 +33,7 @@ export function Tooltip({
className={classNames(
side === "top" && "-mt-7",
side === "right" && "ml-2",
- "bg-inverted text-inverted relative rounded-md px-2 py-1 text-xs font-semibold shadow-lg"
+ "bg-inverted text-inverted relative relative z-20 rounded-md px-2 py-1 text-xs font-semibold shadow-lg"
)}
side={side}
align="center"
diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx
index 707aa31923..5d86e20889 100644
--- a/packages/ui/index.tsx
+++ b/packages/ui/index.tsx
@@ -33,7 +33,6 @@ export {
DateRangePicker,
MultiSelectCheckbox,
ToggleGroup,
- ToggleGroupItem,
getReactSelectProps,
ColorPicker,
FormStep,
diff --git a/yarn.lock b/yarn.lock
index b671ebaec5..e1ee54d140 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -122,25 +122,6 @@ __metadata:
languageName: node
linkType: hard
-"@auth/core@npm:^0.1.4":
- version: 0.1.4
- resolution: "@auth/core@npm:0.1.4"
- dependencies:
- "@panva/hkdf": 1.0.2
- cookie: 0.5.0
- jose: 4.11.1
- oauth4webapi: 2.0.5
- preact: 10.11.3
- preact-render-to-string: 5.2.3
- peerDependencies:
- nodemailer: 6.8.0
- peerDependenciesMeta:
- nodemailer:
- optional: true
- checksum: 64854404ea1883e0deb5535b34bed95cd43fc85094aeaf4f15a79e14045020eb944f844defe857edfc8528a0a024be89cbb2a3069dedef0e9217a74ca6c3eb79
- languageName: node
- linkType: hard
-
"@aws-crypto/ie11-detection@npm:^3.0.0":
version: 3.0.0
resolution: "@aws-crypto/ie11-detection@npm:3.0.0"
@@ -3083,7 +3064,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/plugin-transform-react-jsx-source@npm:^7.16.7":
+"@babel/plugin-transform-react-jsx-source@npm:^7.16.7, @babel/plugin-transform-react-jsx-source@npm:^7.19.6":
version: 7.19.6
resolution: "@babel/plugin-transform-react-jsx-source@npm:7.19.6"
dependencies:
@@ -3120,7 +3101,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/plugin-transform-react-jsx@npm:^7.17.3":
+"@babel/plugin-transform-react-jsx@npm:^7.17.3, @babel/plugin-transform-react-jsx@npm:^7.19.0":
version: 7.21.0
resolution: "@babel/plugin-transform-react-jsx@npm:7.21.0"
dependencies:
@@ -3897,6 +3878,8 @@ __metadata:
"@calcom/core": "*"
"@calcom/dayjs": "*"
"@calcom/emails": "*"
+ "@calcom/embed-core": "*"
+ "@calcom/embed-snippet": "*"
"@calcom/features": "*"
"@calcom/lib": "*"
"@calcom/prisma": "*"
@@ -3909,16 +3892,17 @@ __metadata:
jest: ^28.1.0
memory-cache: ^0.2.0
modify-response-middleware: ^1.1.0
- next: ^13.2.1
+ next: ^12.3.1
next-api-middleware: ^1.0.1
- next-axiom: ^0.16.0
+ next-axiom: ^0.10.0
next-swagger-doc: ^0.3.4
+ next-transpile-modules: ^9.0.0
next-validations: ^0.2.0
node-mocks-http: ^1.11.0
- typescript: ^4.9.4
+ typescript: ^4.7.4
tzdata: ^1.0.30
uuid: ^8.3.2
- zod: ^3.20.2
+ zod: ^3.19.1
languageName: unknown
linkType: soft
@@ -3980,36 +3964,17 @@ __metadata:
languageName: unknown
linkType: soft
-"@calcom/auth@workspace:apps/auth":
+"@calcom/atoms@workspace:packages/atoms":
version: 0.0.0-use.local
- resolution: "@calcom/auth@workspace:apps/auth"
+ resolution: "@calcom/atoms@workspace:packages/atoms"
dependencies:
- "@auth/core": ^0.1.4
- "@calcom/app-store": "*"
- "@calcom/app-store-cli": "*"
- "@calcom/config": "*"
- "@calcom/core": "*"
- "@calcom/dayjs": "*"
- "@calcom/embed-core": "workspace:*"
- "@calcom/embed-react": "workspace:*"
- "@calcom/embed-snippet": "workspace:*"
- "@calcom/features": "*"
- "@calcom/lib": "*"
- "@calcom/prisma": "*"
- "@calcom/trpc": "*"
- "@calcom/tsconfig": "*"
- "@calcom/types": "*"
- "@calcom/ui": "*"
- "@types/node": 16.9.1
- "@types/react": 18.0.26
- "@types/react-dom": 18.0.9
- eslint: ^8.34.0
- eslint-config-next: ^13.2.1
- next: ^13.2.1
- next-auth: ^4.20.1
- react: ^18.2.0
- react-dom: ^18.2.0
- typescript: ^4.9.4
+ "@rollup/plugin-node-resolve": ^15.0.1
+ "@types/react": ^18.0.25
+ "@types/react-dom": ^18.0.9
+ "@vitejs/plugin-react": ^2.2.0
+ rollup-plugin-node-builtins: ^2.1.2
+ typescript: ^4.9.3
+ vite: ^3.2.4
languageName: unknown
linkType: soft
@@ -4050,6 +4015,7 @@ __metadata:
resolution: "@calcom/config@workspace:packages/config"
dependencies:
"@calcom/eslint-plugin-eslint": "*"
+ "@savvywombat/tailwindcss-grid-areas": ^3.0.0
"@tailwindcss/forms": ^0.5.2
"@tailwindcss/line-clamp": ^0.4.0
"@tailwindcss/typography": ^0.5.4
@@ -4077,34 +4043,37 @@ __metadata:
resolution: "@calcom/console@workspace:apps/console"
dependencies:
"@calcom/dayjs": "*"
+ "@calcom/embed-react": "*"
"@calcom/features": "*"
"@calcom/lib": "*"
"@calcom/tsconfig": "*"
"@calcom/ui": "*"
"@headlessui/react": ^1.5.0
"@heroicons/react": ^1.0.6
- "@prisma/client": ^4.13.0
+ "@prisma/client": ^4.7.1
"@tailwindcss/forms": ^0.5.2
"@types/node": 16.9.1
- "@types/react": 18.0.26
+ "@types/react": ^18.0.17
autoprefixer: ^10.4.12
chart.js: ^3.7.1
client-only: ^0.0.1
- eslint: ^8.34.0
- next: ^13.2.1
- next-auth: ^4.20.1
+ eslint: ^8.22.0
+ next: ^12.3.1
+ next-auth: ^4.10.3
next-i18next: ^11.3.0
+ next-transpile-modules: ^9.0.0
postcss: ^8.4.18
- prisma: ^4.13.0
+ prisma: ^4.7.1
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-hook-form: ^7.34.2
react-live-chat-loader: ^2.7.3
swr: ^1.2.2
tailwindcss: ^3.2.1
- typescript: ^4.9.4
+ turbo: ^1.4.3
+ typescript: ^4.7.4
zod: ^3.20.2
languageName: unknown
linkType: soft
@@ -4207,7 +4176,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@calcom/embed-core@workspace:*, @calcom/embed-core@workspace:packages/embeds/embed-core":
+"@calcom/embed-core@*, @calcom/embed-core@workspace:*, @calcom/embed-core@workspace:packages/embeds/embed-core":
version: 0.0.0-use.local
resolution: "@calcom/embed-core@workspace:packages/embeds/embed-core"
dependencies:
@@ -4221,7 +4190,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@calcom/embed-react@workspace:*, @calcom/embed-react@workspace:^, @calcom/embed-react@workspace:packages/embeds/embed-react":
+"@calcom/embed-react@*, @calcom/embed-react@workspace:*, @calcom/embed-react@workspace:^, @calcom/embed-react@workspace:packages/embeds/embed-react":
version: 0.0.0-use.local
resolution: "@calcom/embed-react@workspace:packages/embeds/embed-react"
dependencies:
@@ -4241,7 +4210,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@calcom/embed-snippet@workspace:*, @calcom/embed-snippet@workspace:packages/embeds/embed-snippet":
+"@calcom/embed-snippet@*, @calcom/embed-snippet@workspace:*, @calcom/embed-snippet@workspace:packages/embeds/embed-snippet":
version: 0.0.0-use.local
resolution: "@calcom/embed-snippet@workspace:packages/embeds/embed-snippet"
dependencies:
@@ -4349,9 +4318,13 @@ __metadata:
"@calcom/trpc": "*"
"@calcom/ui": "*"
"@lexical/react": ^0.5.0
+ "@testing-library/react-hooks": ^8.0.1
dompurify: ^2.4.1
+ framer-motion: ^10.12.3
lexical: ^0.5.0
- zustand: ^4.1.4
+ mockdate: ^3.0.5
+ react-sticky-box: ^2.0.4
+ zustand: ^4.3.2
languageName: unknown
linkType: soft
@@ -5370,6 +5343,22 @@ __metadata:
languageName: node
linkType: hard
+"@emotion/is-prop-valid@npm:^0.8.2":
+ version: 0.8.8
+ resolution: "@emotion/is-prop-valid@npm:0.8.8"
+ dependencies:
+ "@emotion/memoize": 0.7.4
+ checksum: bb7ec6d48c572c540e24e47cc94fc2f8dec2d6a342ae97bc9c8b6388d9b8d283862672172a1bb62d335c02662afe6291e10c71e9b8642664a8b43416cdceffac
+ languageName: node
+ linkType: hard
+
+"@emotion/memoize@npm:0.7.4":
+ version: 0.7.4
+ resolution: "@emotion/memoize@npm:0.7.4"
+ checksum: 4e3920d4ec95995657a37beb43d3f4b7d89fed6caa2b173a4c04d10482d089d5c3ea50bbc96618d918b020f26ed6e9c4026bbd45433566576c1f7b056c3271dc
+ languageName: node
+ linkType: hard
+
"@emotion/memoize@npm:^0.7.4, @emotion/memoize@npm:^0.7.5":
version: 0.7.5
resolution: "@emotion/memoize@npm:0.7.5"
@@ -5461,6 +5450,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm@npm:0.15.18":
+ version: 0.15.18
+ resolution: "@esbuild/android-arm@npm:0.15.18"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm@npm:0.16.17":
version: 0.16.17
resolution: "@esbuild/android-arm@npm:0.16.17"
@@ -5531,6 +5527,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-loong64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "@esbuild/linux-loong64@npm:0.15.18"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.16.17":
version: 0.16.17
resolution: "@esbuild/linux-loong64@npm:0.16.17"
@@ -5615,6 +5618,24 @@ __metadata:
languageName: node
linkType: hard
+"@eslint-community/eslint-utils@npm:^4.2.0":
+ version: 4.4.0
+ resolution: "@eslint-community/eslint-utils@npm:4.4.0"
+ dependencies:
+ eslint-visitor-keys: ^3.3.0
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ checksum: cdfe3ae42b4f572cbfb46d20edafe6f36fc5fb52bf2d90875c58aefe226892b9677fef60820e2832caf864a326fe4fc225714c46e8389ccca04d5f9288aabd22
+ languageName: node
+ linkType: hard
+
+"@eslint-community/regexpp@npm:^4.4.0":
+ version: 4.5.0
+ resolution: "@eslint-community/regexpp@npm:4.5.0"
+ checksum: 99c01335947dbd7f2129e954413067e217ccaa4e219fe0917b7d2bd96135789384b8fedbfb8eb09584d5130b27a7b876a7150ab7376f51b3a0c377d5ce026a10
+ languageName: node
+ linkType: hard
+
"@eslint/eslintrc@npm:^1.0.5":
version: 1.3.0
resolution: "@eslint/eslintrc@npm:1.3.0"
@@ -5649,6 +5670,30 @@ __metadata:
languageName: node
linkType: hard
+"@eslint/eslintrc@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "@eslint/eslintrc@npm:2.0.2"
+ dependencies:
+ ajv: ^6.12.4
+ debug: ^4.3.2
+ espree: ^9.5.1
+ globals: ^13.19.0
+ ignore: ^5.2.0
+ import-fresh: ^3.2.1
+ js-yaml: ^4.1.0
+ minimatch: ^3.1.2
+ strip-json-comments: ^3.1.1
+ checksum: cfcf5e12c7b2c4476482e7f12434e76eae16fcd163ee627309adb10b761e5caa4a4e52ed7be464423320ff3d11eca5b50de5bf8be3e25834222470835dd5c801
+ languageName: node
+ linkType: hard
+
+"@eslint/js@npm:8.38.0":
+ version: 8.38.0
+ resolution: "@eslint/js@npm:8.38.0"
+ checksum: 1f28987aa8c9cd93e23384e16c7220863b39b5dc4b66e46d7cdbccce868040f455a98d24cd8b567a884f26545a0555b761f7328d4a00c051e7ef689cbea5fce1
+ languageName: node
+ linkType: hard
+
"@ethereumjs/common@npm:^2.5.0, @ethereumjs/common@npm:^2.6.3":
version: 2.6.3
resolution: "@ethereumjs/common@npm:2.6.3"
@@ -7949,6 +7994,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/env@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/env@npm:12.3.4"
+ checksum: daa3fc11efd1344c503eab41311a0e503ba7fd08607eeb3dc571036a6211eb37959cc4ed48b71dcc411cc214e7623ffd02411080aad3e09dc6a1192d5b256e60
+ languageName: node
+ linkType: hard
+
"@next/env@npm:13.2.3":
version: 13.2.3
resolution: "@next/env@npm:13.2.3"
@@ -7972,6 +8024,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-android-arm-eabi@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-android-arm-eabi@npm:12.3.4"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@next/swc-android-arm-eabi@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-android-arm-eabi@npm:13.2.3"
@@ -7986,6 +8045,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-android-arm64@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-android-arm64@npm:12.3.4"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@next/swc-android-arm64@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-android-arm64@npm:13.2.3"
@@ -8000,6 +8066,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-darwin-arm64@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-darwin-arm64@npm:12.3.4"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@next/swc-darwin-arm64@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-darwin-arm64@npm:13.2.3"
@@ -8014,6 +8087,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-darwin-x64@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-darwin-x64@npm:12.3.4"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@next/swc-darwin-x64@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-darwin-x64@npm:13.2.3"
@@ -8028,6 +8108,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-freebsd-x64@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-freebsd-x64@npm:12.3.4"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@next/swc-freebsd-x64@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-freebsd-x64@npm:13.2.3"
@@ -8042,6 +8129,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-linux-arm-gnueabihf@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-linux-arm-gnueabihf@npm:12.3.4"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@next/swc-linux-arm-gnueabihf@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-linux-arm-gnueabihf@npm:13.2.3"
@@ -8056,6 +8150,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-linux-arm64-gnu@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-linux-arm64-gnu@npm:12.3.4"
+ conditions: os=linux & cpu=arm64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@next/swc-linux-arm64-gnu@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-linux-arm64-gnu@npm:13.2.3"
@@ -8070,6 +8171,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-linux-arm64-musl@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-linux-arm64-musl@npm:12.3.4"
+ conditions: os=linux & cpu=arm64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@next/swc-linux-arm64-musl@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-linux-arm64-musl@npm:13.2.3"
@@ -8084,6 +8192,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-linux-x64-gnu@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-linux-x64-gnu@npm:12.3.4"
+ conditions: os=linux & cpu=x64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@next/swc-linux-x64-gnu@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-linux-x64-gnu@npm:13.2.3"
@@ -8098,6 +8213,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-linux-x64-musl@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-linux-x64-musl@npm:12.3.4"
+ conditions: os=linux & cpu=x64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@next/swc-linux-x64-musl@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-linux-x64-musl@npm:13.2.3"
@@ -8112,6 +8234,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-win32-arm64-msvc@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-win32-arm64-msvc@npm:12.3.4"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@next/swc-win32-arm64-msvc@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-win32-arm64-msvc@npm:13.2.3"
@@ -8126,6 +8255,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-win32-ia32-msvc@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-win32-ia32-msvc@npm:12.3.4"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@next/swc-win32-ia32-msvc@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-win32-ia32-msvc@npm:13.2.3"
@@ -8140,6 +8276,13 @@ __metadata:
languageName: node
linkType: hard
+"@next/swc-win32-x64-msvc@npm:12.3.4":
+ version: 12.3.4
+ resolution: "@next/swc-win32-x64-msvc@npm:12.3.4"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@next/swc-win32-x64-msvc@npm:13.2.3":
version: 13.2.3
resolution: "@next/swc-win32-x64-msvc@npm:13.2.3"
@@ -8299,13 +8442,6 @@ __metadata:
languageName: node
linkType: hard
-"@panva/hkdf@npm:1.0.2":
- version: 1.0.2
- resolution: "@panva/hkdf@npm:1.0.2"
- checksum: 75183b4d5ea816ef516dcea70985c610683579a9e2ac540c2d59b9a3ed27eedaff830a43a1c43c1683556a457c92ac66e09109ee995ab173090e4042c4c4bb03
- languageName: node
- linkType: hard
-
"@panva/hkdf@npm:^1.0.2":
version: 1.0.4
resolution: "@panva/hkdf@npm:1.0.4"
@@ -8410,6 +8546,20 @@ __metadata:
languageName: node
linkType: hard
+"@prisma/client@npm:^4.7.1":
+ version: 4.12.0
+ resolution: "@prisma/client@npm:4.12.0"
+ dependencies:
+ "@prisma/engines-version": 4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7
+ peerDependencies:
+ prisma: "*"
+ peerDependenciesMeta:
+ prisma:
+ optional: true
+ checksum: bbd17500ee218a71e765a75b649c56bc0da1903e63d69d716b6a0e6995c8e1cc5265423ba1518a789c3e71b91d93e7937180db2d107e426bd4ad2f51998240f0
+ languageName: node
+ linkType: hard
+
"@prisma/debug@npm:3.8.1":
version: 3.8.1
resolution: "@prisma/debug@npm:3.8.1"
@@ -8432,6 +8582,13 @@ __metadata:
languageName: node
linkType: hard
+"@prisma/engines-version@npm:4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7":
+ version: 4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7
+ resolution: "@prisma/engines-version@npm:4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7"
+ checksum: 54615d6982db9c50eed6132ad7ab3f32ef93f64d36b7f932b0d6109cd54028ea459293833e48b849a5dd968d934bb3cfb275b9a9172934032f95355d6f663dcf
+ languageName: node
+ linkType: hard
+
"@prisma/engines-version@npm:4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a":
version: 4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a
resolution: "@prisma/engines-version@npm:4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a"
@@ -8439,6 +8596,13 @@ __metadata:
languageName: node
linkType: hard
+"@prisma/engines@npm:4.12.0":
+ version: 4.12.0
+ resolution: "@prisma/engines@npm:4.12.0"
+ checksum: 5d226a2c86bee5bc6fa34910ec9ba9a68a1c0248977d0da47964372edcfe773ee6c4bb00e244258f535570f018d883f0ab9e2677cf06471869bb8857f79880f6
+ languageName: node
+ linkType: hard
+
"@prisma/engines@npm:4.13.0":
version: 4.13.0
resolution: "@prisma/engines@npm:4.13.0"
@@ -9816,6 +9980,25 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/plugin-node-resolve@npm:^15.0.1":
+ version: 15.0.1
+ resolution: "@rollup/plugin-node-resolve@npm:15.0.1"
+ dependencies:
+ "@rollup/pluginutils": ^5.0.1
+ "@types/resolve": 1.20.2
+ deepmerge: ^4.2.2
+ is-builtin-module: ^3.2.0
+ is-module: ^1.0.0
+ resolve: ^1.22.1
+ peerDependencies:
+ rollup: ^2.78.0||^3.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+ checksum: 90e30b41626a15ebf02746a83d34b15f9fe9051ddc156a9bf785504f489947980b3bdeb7bf2f80828a9becfe472a03a96d0238328a3e3e2198a482fcac7eb3aa
+ languageName: node
+ linkType: hard
+
"@rollup/pluginutils@npm:^3.1.0":
version: 3.1.0
resolution: "@rollup/pluginutils@npm:3.1.0"
@@ -9862,6 +10045,17 @@ __metadata:
languageName: node
linkType: hard
+"@savvywombat/tailwindcss-grid-areas@npm:^3.0.0":
+ version: 3.0.1
+ resolution: "@savvywombat/tailwindcss-grid-areas@npm:3.0.1"
+ dependencies:
+ lodash: ^4.17.21
+ peerDependencies:
+ tailwindcss: ^3.0.1
+ checksum: 016672a0585b2b1bdd0ae2f8ed821d634d2a5ba7ea26484adbc8cd38499366be80c6e338cd93d8c25cf6e29a8f33293eb7e24ff68184bea3b36ea27b9884dbb1
+ languageName: node
+ linkType: hard
+
"@sendgrid/client@npm:^7.7.0":
version: 7.7.0
resolution: "@sendgrid/client@npm:7.7.0"
@@ -11867,6 +12061,15 @@ __metadata:
languageName: node
linkType: hard
+"@swc/helpers@npm:0.4.11":
+ version: 0.4.11
+ resolution: "@swc/helpers@npm:0.4.11"
+ dependencies:
+ tslib: ^2.4.0
+ checksum: 736857d524b41a8a4db81094e9b027f554004e0fa3e86325d85bdb38f7e6459ce022db079edb6c61ba0f46fe8583b3e663e95f7acbd13e51b8da6c34e45bba2e
+ languageName: node
+ linkType: hard
+
"@swc/helpers@npm:0.4.14":
version: 0.4.14
resolution: "@swc/helpers@npm:0.4.14"
@@ -12024,6 +12227,28 @@ __metadata:
languageName: node
linkType: hard
+"@testing-library/react-hooks@npm:^8.0.1":
+ version: 8.0.1
+ resolution: "@testing-library/react-hooks@npm:8.0.1"
+ dependencies:
+ "@babel/runtime": ^7.12.5
+ react-error-boundary: ^3.1.0
+ peerDependencies:
+ "@types/react": ^16.9.0 || ^17.0.0
+ react: ^16.9.0 || ^17.0.0
+ react-dom: ^16.9.0 || ^17.0.0
+ react-test-renderer: ^16.9.0 || ^17.0.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ react-dom:
+ optional: true
+ react-test-renderer:
+ optional: true
+ checksum: 7fe44352e920deb5cb1876f80d64e48615232072c9d5382f1e0284b3aab46bb1c659a040b774c45cdf084a5257b8fe463f7e08695ad8480d8a15635d4d3d1f6d
+ languageName: node
+ linkType: hard
+
"@testing-library/react@npm:^13.3.0":
version: 13.3.0
resolution: "@testing-library/react@npm:13.3.0"
@@ -13062,6 +13287,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/resolve@npm:1.20.2":
+ version: 1.20.2
+ resolution: "@types/resolve@npm:1.20.2"
+ checksum: 61c2cad2499ffc8eab36e3b773945d337d848d3ac6b7b0a87c805ba814bc838ef2f262fc0f109bfd8d2e0898ff8bd80ad1025f9ff64f1f71d3d4294c9f14e5f6
+ languageName: node
+ linkType: hard
+
"@types/responselike@npm:*, @types/responselike@npm:^1.0.0":
version: 1.0.0
resolution: "@types/responselike@npm:1.0.0"
@@ -13645,6 +13877,23 @@ __metadata:
languageName: node
linkType: hard
+"@vitejs/plugin-react@npm:^2.2.0":
+ version: 2.2.0
+ resolution: "@vitejs/plugin-react@npm:2.2.0"
+ dependencies:
+ "@babel/core": ^7.19.6
+ "@babel/plugin-transform-react-jsx": ^7.19.0
+ "@babel/plugin-transform-react-jsx-development": ^7.18.6
+ "@babel/plugin-transform-react-jsx-self": ^7.18.6
+ "@babel/plugin-transform-react-jsx-source": ^7.19.6
+ magic-string: ^0.26.7
+ react-refresh: ^0.14.0
+ peerDependencies:
+ vite: ^3.0.0
+ checksum: cc85ab31b4689ab137c4b1e65383dccce494371523eb164c579096e513a2abbaa7efb49ba08655fae9f6692f5b7b2602ad339bdce4ae5982fc08fe444fb8a4e5
+ languageName: node
+ linkType: hard
+
"@wagmi/core@npm:^0.5.4":
version: 0.5.4
resolution: "@wagmi/core@npm:0.5.4"
@@ -14349,6 +14598,15 @@ __metadata:
languageName: node
linkType: hard
+"abstract-leveldown@npm:~0.12.0, abstract-leveldown@npm:~0.12.1":
+ version: 0.12.4
+ resolution: "abstract-leveldown@npm:0.12.4"
+ dependencies:
+ xtend: ~3.0.0
+ checksum: e300f04bb638cc9c462f6e8fa925672e51beb24c1470c39ece709e54f2f499661ac5fe0119175c7dcb6e32c843423d6960009d4d24e72526478b261163e8070b
+ languageName: node
+ linkType: hard
+
"accept-language-parser@npm:^1.5.0":
version: 1.5.0
resolution: "accept-language-parser@npm:1.5.0"
@@ -15919,6 +16177,15 @@ __metadata:
languageName: node
linkType: hard
+"bl@npm:~0.8.1":
+ version: 0.8.2
+ resolution: "bl@npm:0.8.2"
+ dependencies:
+ readable-stream: ~1.0.26
+ checksum: 18767c5c861ae1cdbb000bb346e9e8e29137225e8eef97f39db78beeb236beca609f465580c5c1b177d621505f57400834fb4a17a66d264f33a0237293ec2ac5
+ languageName: node
+ linkType: hard
+
"blakejs@npm:^1.1.0":
version: 1.2.1
resolution: "blakejs@npm:1.2.1"
@@ -16183,6 +16450,17 @@ __metadata:
languageName: node
linkType: hard
+"browserify-fs@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "browserify-fs@npm:1.0.0"
+ dependencies:
+ level-filesystem: ^1.0.1
+ level-js: ^2.1.3
+ levelup: ^0.18.2
+ checksum: e0c35cf42c839c0a217048b1671d91ee6e53fd05f163db4f809e46c2f6264f784768e7c850abc200b0eaca378d42e00e01876eda21fd84fc0a4280bd6200a9c3
+ languageName: node
+ linkType: hard
+
"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.0.1":
version: 4.1.0
resolution: "browserify-rsa@npm:4.1.0"
@@ -16383,6 +16661,13 @@ __metadata:
languageName: node
linkType: hard
+"buffer-es6@npm:^4.9.2":
+ version: 4.9.3
+ resolution: "buffer-es6@npm:4.9.3"
+ checksum: dfc8ebb3c5c00166e6f81e6ec7ea876693ea6197a8d0b07b1a17482ffab0e5d3307bfb539f84862b1ae35cd70ad03835db0f3c7dc4e337cbd16c50bb4c7e5df7
+ languageName: node
+ linkType: hard
+
"buffer-fill@npm:^1.0.0":
version: 1.0.0
resolution: "buffer-fill@npm:1.0.0"
@@ -16469,6 +16754,13 @@ __metadata:
languageName: node
linkType: hard
+"builtin-modules@npm:^3.3.0":
+ version: 3.3.0
+ resolution: "builtin-modules@npm:3.3.0"
+ checksum: db021755d7ed8be048f25668fe2117620861ef6703ea2c65ed2779c9e3636d5c3b82325bd912244293959ff3ae303afa3471f6a15bf5060c103e4cc3a839749d
+ languageName: node
+ linkType: hard
+
"builtin-status-codes@npm:^3.0.0":
version: 3.0.0
resolution: "builtin-status-codes@npm:3.0.0"
@@ -17398,6 +17690,13 @@ __metadata:
languageName: node
linkType: hard
+"clone@npm:~0.1.9":
+ version: 0.1.19
+ resolution: "clone@npm:0.1.19"
+ checksum: 5e710e16da67abe30c0664c8fd69c280635be59a4fae0a5fe58ed324e701e99348b48ce67288716fa223edd42ba574e58a3783cb2fcfa381b8b49ce7e56ac3f4
+ languageName: node
+ linkType: hard
+
"clsx@npm:1.1.0":
version: 1.1.0
resolution: "clsx@npm:1.1.0"
@@ -17727,7 +18026,7 @@ __metadata:
languageName: node
linkType: hard
-"concat-stream@npm:^1.5.0":
+"concat-stream@npm:^1.4.4, concat-stream@npm:^1.5.0":
version: 1.6.2
resolution: "concat-stream@npm:1.6.2"
dependencies:
@@ -18838,6 +19137,15 @@ __metadata:
languageName: node
linkType: hard
+"deferred-leveldown@npm:~0.2.0":
+ version: 0.2.0
+ resolution: "deferred-leveldown@npm:0.2.0"
+ dependencies:
+ abstract-leveldown: ~0.12.1
+ checksum: f7690ec5b1e951e6f56998be26dd0a1331ef28cb7eaa9e090a282780d47dc006effd4b82a2a82b636cae801378047997aca10c0b44b09c8624633cdb96b07913
+ languageName: node
+ linkType: hard
+
"define-lazy-prop@npm:^2.0.0":
version: 2.0.0
resolution: "define-lazy-prop@npm:2.0.0"
@@ -19728,7 +20036,7 @@ __metadata:
languageName: node
linkType: hard
-"errno@npm:^0.1.3, errno@npm:~0.1.7":
+"errno@npm:^0.1.1, errno@npm:^0.1.3, errno@npm:~0.1.1, errno@npm:~0.1.7":
version: 0.1.8
resolution: "errno@npm:0.1.8"
dependencies:
@@ -19982,6 +20290,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-android-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-android-64@npm:0.15.18"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-android-arm64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-android-arm64@npm:0.14.54"
@@ -19989,6 +20304,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-android-arm64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-android-arm64@npm:0.15.18"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"esbuild-darwin-64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-darwin-64@npm:0.14.54"
@@ -19996,6 +20318,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-darwin-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-darwin-64@npm:0.15.18"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-darwin-arm64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-darwin-arm64@npm:0.14.54"
@@ -20003,6 +20332,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-darwin-arm64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-darwin-arm64@npm:0.15.18"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"esbuild-freebsd-64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-freebsd-64@npm:0.14.54"
@@ -20010,6 +20346,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-freebsd-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-freebsd-64@npm:0.15.18"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-freebsd-arm64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-freebsd-arm64@npm:0.14.54"
@@ -20017,6 +20360,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-freebsd-arm64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-freebsd-arm64@npm:0.15.18"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"esbuild-linux-32@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-32@npm:0.14.54"
@@ -20024,6 +20374,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-32@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-32@npm:0.15.18"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
"esbuild-linux-64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-64@npm:0.14.54"
@@ -20031,6 +20388,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-64@npm:0.15.18"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-linux-arm64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-arm64@npm:0.14.54"
@@ -20038,6 +20402,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-arm64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-arm64@npm:0.15.18"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"esbuild-linux-arm@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-arm@npm:0.14.54"
@@ -20045,6 +20416,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-arm@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-arm@npm:0.15.18"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"esbuild-linux-mips64le@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-mips64le@npm:0.14.54"
@@ -20052,6 +20430,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-mips64le@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-mips64le@npm:0.15.18"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
"esbuild-linux-ppc64le@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-ppc64le@npm:0.14.54"
@@ -20059,6 +20444,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-ppc64le@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-ppc64le@npm:0.15.18"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"esbuild-linux-riscv64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-riscv64@npm:0.14.54"
@@ -20066,6 +20458,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-riscv64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-riscv64@npm:0.15.18"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
"esbuild-linux-s390x@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-linux-s390x@npm:0.14.54"
@@ -20073,6 +20472,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-linux-s390x@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-linux-s390x@npm:0.15.18"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
"esbuild-netbsd-64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-netbsd-64@npm:0.14.54"
@@ -20080,6 +20486,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-netbsd-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-netbsd-64@npm:0.15.18"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-openbsd-64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-openbsd-64@npm:0.14.54"
@@ -20087,6 +20500,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-openbsd-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-openbsd-64@npm:0.15.18"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-sunos-64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-sunos-64@npm:0.14.54"
@@ -20094,6 +20514,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-sunos-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-sunos-64@npm:0.15.18"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-windows-32@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-windows-32@npm:0.14.54"
@@ -20101,6 +20528,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-windows-32@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-windows-32@npm:0.15.18"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"esbuild-windows-64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-windows-64@npm:0.14.54"
@@ -20108,6 +20542,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-windows-64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-windows-64@npm:0.15.18"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"esbuild-windows-arm64@npm:0.14.54":
version: 0.14.54
resolution: "esbuild-windows-arm64@npm:0.14.54"
@@ -20115,6 +20556,13 @@ __metadata:
languageName: node
linkType: hard
+"esbuild-windows-arm64@npm:0.15.18":
+ version: 0.15.18
+ resolution: "esbuild-windows-arm64@npm:0.15.18"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"esbuild@npm:^0.14.27":
version: 0.14.54
resolution: "esbuild@npm:0.14.54"
@@ -20189,6 +20637,83 @@ __metadata:
languageName: node
linkType: hard
+"esbuild@npm:^0.15.9":
+ version: 0.15.18
+ resolution: "esbuild@npm:0.15.18"
+ dependencies:
+ "@esbuild/android-arm": 0.15.18
+ "@esbuild/linux-loong64": 0.15.18
+ esbuild-android-64: 0.15.18
+ esbuild-android-arm64: 0.15.18
+ esbuild-darwin-64: 0.15.18
+ esbuild-darwin-arm64: 0.15.18
+ esbuild-freebsd-64: 0.15.18
+ esbuild-freebsd-arm64: 0.15.18
+ esbuild-linux-32: 0.15.18
+ esbuild-linux-64: 0.15.18
+ esbuild-linux-arm: 0.15.18
+ esbuild-linux-arm64: 0.15.18
+ esbuild-linux-mips64le: 0.15.18
+ esbuild-linux-ppc64le: 0.15.18
+ esbuild-linux-riscv64: 0.15.18
+ esbuild-linux-s390x: 0.15.18
+ esbuild-netbsd-64: 0.15.18
+ esbuild-openbsd-64: 0.15.18
+ esbuild-sunos-64: 0.15.18
+ esbuild-windows-32: 0.15.18
+ esbuild-windows-64: 0.15.18
+ esbuild-windows-arm64: 0.15.18
+ dependenciesMeta:
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ esbuild-android-64:
+ optional: true
+ esbuild-android-arm64:
+ optional: true
+ esbuild-darwin-64:
+ optional: true
+ esbuild-darwin-arm64:
+ optional: true
+ esbuild-freebsd-64:
+ optional: true
+ esbuild-freebsd-arm64:
+ optional: true
+ esbuild-linux-32:
+ optional: true
+ esbuild-linux-64:
+ optional: true
+ esbuild-linux-arm:
+ optional: true
+ esbuild-linux-arm64:
+ optional: true
+ esbuild-linux-mips64le:
+ optional: true
+ esbuild-linux-ppc64le:
+ optional: true
+ esbuild-linux-riscv64:
+ optional: true
+ esbuild-linux-s390x:
+ optional: true
+ esbuild-netbsd-64:
+ optional: true
+ esbuild-openbsd-64:
+ optional: true
+ esbuild-sunos-64:
+ optional: true
+ esbuild-windows-32:
+ optional: true
+ esbuild-windows-64:
+ optional: true
+ esbuild-windows-arm64:
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: ec12682b2cb2d4f0669d0e555028b87a9284ca7f6a1b26e35e69a8697165b35cc682ad598abc70f0bbcfdc12ca84ef888caf5ceee389237862e8f8c17da85f89
+ languageName: node
+ linkType: hard
+
"esbuild@npm:^0.16.14":
version: 0.16.17
resolution: "esbuild@npm:0.16.17"
@@ -20638,6 +21163,13 @@ __metadata:
languageName: node
linkType: hard
+"eslint-visitor-keys@npm:^3.4.0":
+ version: 3.4.0
+ resolution: "eslint-visitor-keys@npm:3.4.0"
+ checksum: 33159169462d3989321a1ec1e9aaaf6a24cc403d5d347e9886d1b5bfe18ffa1be73bdc6203143a28a606b142b1af49787f33cff0d6d0813eb5f2e8d2e1a6043c
+ languageName: node
+ linkType: hard
+
"eslint@npm:8.4.1":
version: 8.4.1
resolution: "eslint@npm:8.4.1"
@@ -20686,6 +21218,56 @@ __metadata:
languageName: node
linkType: hard
+"eslint@npm:^8.22.0":
+ version: 8.38.0
+ resolution: "eslint@npm:8.38.0"
+ dependencies:
+ "@eslint-community/eslint-utils": ^4.2.0
+ "@eslint-community/regexpp": ^4.4.0
+ "@eslint/eslintrc": ^2.0.2
+ "@eslint/js": 8.38.0
+ "@humanwhocodes/config-array": ^0.11.8
+ "@humanwhocodes/module-importer": ^1.0.1
+ "@nodelib/fs.walk": ^1.2.8
+ ajv: ^6.10.0
+ chalk: ^4.0.0
+ cross-spawn: ^7.0.2
+ debug: ^4.3.2
+ doctrine: ^3.0.0
+ escape-string-regexp: ^4.0.0
+ eslint-scope: ^7.1.1
+ eslint-visitor-keys: ^3.4.0
+ espree: ^9.5.1
+ esquery: ^1.4.2
+ esutils: ^2.0.2
+ fast-deep-equal: ^3.1.3
+ file-entry-cache: ^6.0.1
+ find-up: ^5.0.0
+ glob-parent: ^6.0.2
+ globals: ^13.19.0
+ grapheme-splitter: ^1.0.4
+ ignore: ^5.2.0
+ import-fresh: ^3.0.0
+ imurmurhash: ^0.1.4
+ is-glob: ^4.0.0
+ is-path-inside: ^3.0.3
+ js-sdsl: ^4.1.4
+ js-yaml: ^4.1.0
+ json-stable-stringify-without-jsonify: ^1.0.1
+ levn: ^0.4.1
+ lodash.merge: ^4.6.2
+ minimatch: ^3.1.2
+ natural-compare: ^1.4.0
+ optionator: ^0.9.1
+ strip-ansi: ^6.0.1
+ strip-json-comments: ^3.1.0
+ text-table: ^0.2.0
+ bin:
+ eslint: bin/eslint.js
+ checksum: 73b6d9b650d0434aa7c07d0a1802f099b086ee70a8d8ba7be730439a26572a5eb71def12125c82942be2ec8ee5be38a6f1b42a13e40d4b67f11a148ec9e263eb
+ languageName: node
+ linkType: hard
+
"eslint@npm:^8.34.0":
version: 8.34.0
resolution: "eslint@npm:8.34.0"
@@ -20779,6 +21361,17 @@ __metadata:
languageName: node
linkType: hard
+"espree@npm:^9.5.1":
+ version: 9.5.1
+ resolution: "espree@npm:9.5.1"
+ dependencies:
+ acorn: ^8.8.0
+ acorn-jsx: ^5.3.2
+ eslint-visitor-keys: ^3.4.0
+ checksum: cdf6e43540433d917c4f2ee087c6e987b2063baa85a1d9cdaf51533d78275ebd5910c42154e7baf8e3e89804b386da0a2f7fad2264d8f04420e7506bf87b3b88
+ languageName: node
+ linkType: hard
+
"esprima@npm:^4.0.0, esprima@npm:^4.0.1":
version: 4.0.1
resolution: "esprima@npm:4.0.1"
@@ -20798,6 +21391,15 @@ __metadata:
languageName: node
linkType: hard
+"esquery@npm:^1.4.2":
+ version: 1.5.0
+ resolution: "esquery@npm:1.5.0"
+ dependencies:
+ estraverse: ^5.1.0
+ checksum: aefb0d2596c230118656cd4ec7532d447333a410a48834d80ea648b1e7b5c9bc9ed8b5e33a89cb04e487b60d622f44cf5713bf4abed7c97343edefdc84a35900
+ languageName: node
+ linkType: hard
+
"esrecurse@npm:^4.1.0, esrecurse@npm:^4.3.0":
version: 4.3.0
resolution: "esrecurse@npm:4.3.0"
@@ -22105,6 +22707,13 @@ __metadata:
languageName: node
linkType: hard
+"foreach@npm:~2.0.1":
+ version: 2.0.6
+ resolution: "foreach@npm:2.0.6"
+ checksum: f7b68494545ee41cbd0b0425ebf5386c265dc38ef2a9b0d5cd91a1b82172e939b4cf9387f8e0ebf6db4e368fc79ed323f2198424d5c774515ac3ed9b08901c0e
+ languageName: node
+ linkType: hard
+
"foreground-child@npm:^2.0.0":
version: 2.0.0
resolution: "foreground-child@npm:2.0.0"
@@ -22284,6 +22893,27 @@ __metadata:
languageName: node
linkType: hard
+"framer-motion@npm:^10.12.3":
+ version: 10.12.3
+ resolution: "framer-motion@npm:10.12.3"
+ dependencies:
+ "@emotion/is-prop-valid": ^0.8.2
+ tslib: ^2.4.0
+ peerDependencies:
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ dependenciesMeta:
+ "@emotion/is-prop-valid":
+ optional: true
+ peerDependenciesMeta:
+ react:
+ optional: true
+ react-dom:
+ optional: true
+ checksum: c292da47b5bcb313e3db2ffe19e61b3c76bf59f4a45dc72f62a3d9b33f58533d420aced47d5e9eb06be20be97651c937ae91aebb93d8bad6d0412c2768715956
+ languageName: node
+ linkType: hard
+
"fresh@npm:0.5.2, fresh@npm:^0.5.2":
version: 0.5.2
resolution: "fresh@npm:0.5.2"
@@ -22503,6 +23133,15 @@ __metadata:
languageName: node
linkType: hard
+"fwd-stream@npm:^1.0.4":
+ version: 1.0.4
+ resolution: "fwd-stream@npm:1.0.4"
+ dependencies:
+ readable-stream: ~1.0.26-4
+ checksum: db4dcf68f214b3fabd6cd9658630dfd1d7ed8d43f7f45408027a90220cd75276e782d1e958821775d7a3a4a83034778e75a097bdc7002c758e8896f76213c65d
+ languageName: node
+ linkType: hard
+
"gauge@npm:^3.0.0":
version: 3.0.2
resolution: "gauge@npm:3.0.2"
@@ -24111,6 +24750,13 @@ __metadata:
languageName: node
linkType: hard
+"idb-wrapper@npm:^1.5.0":
+ version: 1.7.2
+ resolution: "idb-wrapper@npm:1.7.2"
+ checksum: a5fa3a771166205e2d5d2b93c66bd31571dada3526b59bc0f8583efb091b6b327125f1a964a25a281b85ef1c44af10a3c511652632ad3adf8229a161132d66ae
+ languageName: node
+ linkType: hard
+
"idna-uts46-hx@npm:^2.3.1":
version: 2.3.1
resolution: "idna-uts46-hx@npm:2.3.1"
@@ -24255,6 +24901,13 @@ __metadata:
languageName: node
linkType: hard
+"indexof@npm:~0.0.1":
+ version: 0.0.1
+ resolution: "indexof@npm:0.0.1"
+ checksum: 0fb04e8b147b8585d981a6df1564f25bb3678d6fa74e33e5cecc1464b10f78e15e8ef6bb688f135fe5c2844a128fac8a7831cbe5adc81fdcf12681b093dfcc25
+ languageName: node
+ linkType: hard
+
"infer-owner@npm:^1.0.3, infer-owner@npm:^1.0.4":
version: 1.0.4
resolution: "infer-owner@npm:1.0.4"
@@ -24622,6 +25275,15 @@ __metadata:
languageName: node
linkType: hard
+"is-builtin-module@npm:^3.2.0":
+ version: 3.2.1
+ resolution: "is-builtin-module@npm:3.2.1"
+ dependencies:
+ builtin-modules: ^3.3.0
+ checksum: e8f0ffc19a98240bda9c7ada84d846486365af88d14616e737d280d378695c8c448a621dcafc8332dbf0fcd0a17b0763b845400709963fa9151ddffece90ae88
+ languageName: node
+ linkType: hard
+
"is-callable@npm:^1.1.4, is-callable@npm:^1.2.4":
version: 1.2.4
resolution: "is-callable@npm:1.2.4"
@@ -24899,6 +25561,13 @@ __metadata:
languageName: node
linkType: hard
+"is-module@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "is-module@npm:1.0.0"
+ checksum: 8cd5390730c7976fb4e8546dd0b38865ee6f7bacfa08dfbb2cc07219606755f0b01709d9361e01f13009bbbd8099fa2927a8ed665118a6105d66e40f1b838c3f
+ languageName: node
+ linkType: hard
+
"is-nan@npm:^1.3.2":
version: 1.3.2
resolution: "is-nan@npm:1.3.2"
@@ -24969,6 +25638,13 @@ __metadata:
languageName: node
linkType: hard
+"is-object@npm:~0.1.2":
+ version: 0.1.2
+ resolution: "is-object@npm:0.1.2"
+ checksum: 7e500b15f4748278ea0a8d43b1283e75e866c055e4a790389087ce652eab8a9343fd74710738f0fdf13a323c31330d65bdcc106f38e9bb7bc0b9c60ae3fd2a2d
+ languageName: node
+ linkType: hard
+
"is-path-inside@npm:^3.0.3":
version: 3.0.3
resolution: "is-path-inside@npm:3.0.3"
@@ -25227,6 +25903,20 @@ __metadata:
languageName: node
linkType: hard
+"is@npm:~0.2.6":
+ version: 0.2.7
+ resolution: "is@npm:0.2.7"
+ checksum: 45cea1e6deb41150b5753e18041a833657313e9c791c73f96fb9014b613346f5af2e6650858ef50ea6262c79555b65e09b13d30a268139863885025dd65f1059
+ languageName: node
+ linkType: hard
+
+"isarray@npm:0.0.1":
+ version: 0.0.1
+ resolution: "isarray@npm:0.0.1"
+ checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4
+ languageName: node
+ linkType: hard
+
"isarray@npm:1.0.0, isarray@npm:^1.0.0, isarray@npm:~1.0.0":
version: 1.0.0
resolution: "isarray@npm:1.0.0"
@@ -25241,6 +25931,13 @@ __metadata:
languageName: node
linkType: hard
+"isbuffer@npm:~0.0.0":
+ version: 0.0.0
+ resolution: "isbuffer@npm:0.0.0"
+ checksum: 9796296d3c493974c1f71ccf3170cc8007217a19ce8b3b9dedffd32e8ccc3ac42473b572bbf1b24b86143e826ea157aead11fd1285389518abab76c7da5f50ed
+ languageName: node
+ linkType: hard
+
"isexe@npm:^2.0.0":
version: 2.0.0
resolution: "isexe@npm:2.0.0"
@@ -26047,13 +26744,6 @@ __metadata:
languageName: node
linkType: hard
-"jose@npm:4.11.1":
- version: 4.11.1
- resolution: "jose@npm:4.11.1"
- checksum: cd15cba258d0fd20f6168631ce2e94fda8442df80e43c1033c523915cecdf390a1cc8efe0eab0c2d65935ca973d791c668aea80724d2aa9c2879d4e70f3081d7
- languageName: node
- linkType: hard
-
"jose@npm:4.12.0":
version: 4.12.0
resolution: "jose@npm:4.12.0"
@@ -26741,6 +27431,109 @@ __metadata:
languageName: node
linkType: hard
+"level-blobs@npm:^0.1.7":
+ version: 0.1.7
+ resolution: "level-blobs@npm:0.1.7"
+ dependencies:
+ level-peek: 1.0.6
+ once: ^1.3.0
+ readable-stream: ^1.0.26-4
+ checksum: e3cf78ef0bc64ff350edb4e247b2689cd4f5facf1119694ca8c96c28a05a38dc9d88e0bd065b18af65330bc22f5d588719a5c3e63adaa5feba5ea7913f87bebe
+ languageName: node
+ linkType: hard
+
+"level-filesystem@npm:^1.0.1":
+ version: 1.2.0
+ resolution: "level-filesystem@npm:1.2.0"
+ dependencies:
+ concat-stream: ^1.4.4
+ errno: ^0.1.1
+ fwd-stream: ^1.0.4
+ level-blobs: ^0.1.7
+ level-peek: ^1.0.6
+ level-sublevel: ^5.2.0
+ octal: ^1.0.0
+ once: ^1.3.0
+ xtend: ^2.2.0
+ checksum: a29e6a9d8c1879d43610113d1bcb59368685ec0ae413fcf0f8dcbb0a0c26b88fcf16f7481acb2b4650e5951ba0635e73a2c8fbe25cd599c50f80949a5547a367
+ languageName: node
+ linkType: hard
+
+"level-fix-range@npm:2.0":
+ version: 2.0.0
+ resolution: "level-fix-range@npm:2.0.0"
+ dependencies:
+ clone: ~0.1.9
+ checksum: 250cefa69e1035d1412b4ba3e5cab83cceb894aa833fb0a93417d8d6230c60f6f8154feffbd0f116461ddd441b909e7df1323355d3e1769b3bb20a55729145b5
+ languageName: node
+ linkType: hard
+
+"level-fix-range@npm:~1.0.2":
+ version: 1.0.2
+ resolution: "level-fix-range@npm:1.0.2"
+ checksum: 6c9a3894ea08947fae79c41b75e8b9d57979523b656bec43c589f2dc4455276a150df445d9a7ca880a7c58c2ef19f5cea7f661d777993b870f4943af6b31d5bb
+ languageName: node
+ linkType: hard
+
+"level-hooks@npm:>=4.4.0 <5":
+ version: 4.5.0
+ resolution: "level-hooks@npm:4.5.0"
+ dependencies:
+ string-range: ~1.2
+ checksum: f198ad2e0901a4719e324e67f546097589af79665ebaaabee7122fda18a41ada3158bb1816b8b82430f30c68610125e4e20b5c09ec3ba7ae262d97dba34f48ab
+ languageName: node
+ linkType: hard
+
+"level-js@npm:^2.1.3":
+ version: 2.2.4
+ resolution: "level-js@npm:2.2.4"
+ dependencies:
+ abstract-leveldown: ~0.12.0
+ idb-wrapper: ^1.5.0
+ isbuffer: ~0.0.0
+ ltgt: ^2.1.2
+ typedarray-to-buffer: ~1.0.0
+ xtend: ~2.1.2
+ checksum: 4fed784fcfad4bc6ec97d9c3897e95eaa30326fcdab9f4c7437624d10fa875fa84aafcc2acac0d53181af506cbc012c03f413b4da12ff83758d3bcbb699f8c8e
+ languageName: node
+ linkType: hard
+
+"level-peek@npm:1.0.6, level-peek@npm:^1.0.6":
+ version: 1.0.6
+ resolution: "level-peek@npm:1.0.6"
+ dependencies:
+ level-fix-range: ~1.0.2
+ checksum: e07d5f8b80675727204d9a226a249139da9e354e633b9d57b7a5186a7b85be445e550ca628f5133bf7a220a9311a193ded5a3f83588dc4eaa53ffb86b426154a
+ languageName: node
+ linkType: hard
+
+"level-sublevel@npm:^5.2.0":
+ version: 5.2.3
+ resolution: "level-sublevel@npm:5.2.3"
+ dependencies:
+ level-fix-range: 2.0
+ level-hooks: ">=4.4.0 <5"
+ string-range: ~1.2.1
+ xtend: ~2.0.4
+ checksum: f0fdffc2f9ca289aa183a1bf7f300a8f92e4f01be60eab37ab36e1f6ec33ed449519d8f69504a616e82f3ddca13a15fa4e19af1dcc1beba9044a4c60b6cd94bf
+ languageName: node
+ linkType: hard
+
+"levelup@npm:^0.18.2":
+ version: 0.18.6
+ resolution: "levelup@npm:0.18.6"
+ dependencies:
+ bl: ~0.8.1
+ deferred-leveldown: ~0.2.0
+ errno: ~0.1.1
+ prr: ~0.0.0
+ readable-stream: ~1.0.26
+ semver: ~2.3.1
+ xtend: ~3.0.0
+ checksum: 80e140dd83dc94050e283fc02874ae85116cb560d81e14fee0ac111f86006887835ec905dca7a081414c07eca202245a580f1e02f696367b777ecc23a9e05b86
+ languageName: node
+ linkType: hard
+
"leven@npm:^3.1.0":
version: 3.1.0
resolution: "leven@npm:3.1.0"
@@ -27399,6 +28192,13 @@ __metadata:
languageName: node
linkType: hard
+"ltgt@npm:^2.1.2":
+ version: 2.2.1
+ resolution: "ltgt@npm:2.2.1"
+ checksum: 7e3874296f7538bc8087b428ac4208008d7b76916354b34a08818ca7c83958c1df10ec427eeeaad895f6b81e41e24745b18d30f89abcc21d228b94f6961d50a2
+ languageName: node
+ linkType: hard
+
"lucide-react@npm:^0.125.0":
version: 0.125.0
resolution: "lucide-react@npm:0.125.0"
@@ -27451,6 +28251,15 @@ __metadata:
languageName: node
linkType: hard
+"magic-string@npm:^0.26.7":
+ version: 0.26.7
+ resolution: "magic-string@npm:0.26.7"
+ dependencies:
+ sourcemap-codec: ^1.4.8
+ checksum: 89b0d60cbb32bbf3d1e23c46ea93db082d18a8230b972027aecb10a40bba51be519ecce0674f995571e3affe917b76b09f59d8dbc9a1b2c9c4102a2b6e8a2b01
+ languageName: node
+ linkType: hard
+
"magic-string@npm:^0.27.0":
version: 0.27.0
resolution: "magic-string@npm:0.27.0"
@@ -29335,6 +30144,31 @@ __metadata:
languageName: node
linkType: hard
+"next-auth@npm:^4.10.3":
+ version: 4.22.0
+ resolution: "next-auth@npm:4.22.0"
+ 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: 327a7715a963c890afd0b5a47316ce2c4cec887a86d96f2c208135df0f10b52c6e1fdcbb436a77926c24695165beea3bd0588da2411d5dec12fa19baae0c2533
+ languageName: node
+ linkType: hard
+
"next-auth@npm:^4.20.1":
version: 4.20.1
resolution: "next-auth@npm:4.20.1"
@@ -29360,6 +30194,17 @@ __metadata:
languageName: node
linkType: hard
+"next-axiom@npm:^0.10.0":
+ version: 0.10.0
+ resolution: "next-axiom@npm:0.10.0"
+ dependencies:
+ whatwg-fetch: ^3.6.2
+ peerDependencies:
+ next: ^12.1.4
+ checksum: 57aea4edbdab1a4da59eb16644e850a082a23657128f1f953a2b3601b09fa4558090d42b616f8a3e6022c0deef0d95937321f373caa9ca591178dd8445528921
+ languageName: node
+ linkType: hard
+
"next-axiom@npm:^0.16.0":
version: 0.16.0
resolution: "next-axiom@npm:0.16.0"
@@ -29467,6 +30312,16 @@ __metadata:
languageName: node
linkType: hard
+"next-transpile-modules@npm:^9.0.0":
+ version: 9.1.0
+ resolution: "next-transpile-modules@npm:9.1.0"
+ dependencies:
+ enhanced-resolve: ^5.10.0
+ escalade: ^3.1.1
+ checksum: 8cc46196db3c2d2063fb29fe5b4d03c21065ab08130085b24d61e4ed512d99c12083d28179771cf02f70f8bf5970db0781a228aacf6cc61662dbdcabaddfc472
+ languageName: node
+ linkType: hard
+
"next-validations@npm:^0.2.0":
version: 0.2.1
resolution: "next-validations@npm:0.2.1"
@@ -29547,6 +30402,75 @@ __metadata:
languageName: node
linkType: hard
+"next@npm:^12.3.1":
+ version: 12.3.4
+ resolution: "next@npm:12.3.4"
+ dependencies:
+ "@next/env": 12.3.4
+ "@next/swc-android-arm-eabi": 12.3.4
+ "@next/swc-android-arm64": 12.3.4
+ "@next/swc-darwin-arm64": 12.3.4
+ "@next/swc-darwin-x64": 12.3.4
+ "@next/swc-freebsd-x64": 12.3.4
+ "@next/swc-linux-arm-gnueabihf": 12.3.4
+ "@next/swc-linux-arm64-gnu": 12.3.4
+ "@next/swc-linux-arm64-musl": 12.3.4
+ "@next/swc-linux-x64-gnu": 12.3.4
+ "@next/swc-linux-x64-musl": 12.3.4
+ "@next/swc-win32-arm64-msvc": 12.3.4
+ "@next/swc-win32-ia32-msvc": 12.3.4
+ "@next/swc-win32-x64-msvc": 12.3.4
+ "@swc/helpers": 0.4.11
+ caniuse-lite: ^1.0.30001406
+ postcss: 8.4.14
+ styled-jsx: 5.0.7
+ use-sync-external-store: 1.2.0
+ peerDependencies:
+ fibers: ">= 3.1.0"
+ node-sass: ^6.0.0 || ^7.0.0
+ react: ^17.0.2 || ^18.0.0-0
+ react-dom: ^17.0.2 || ^18.0.0-0
+ sass: ^1.3.0
+ dependenciesMeta:
+ "@next/swc-android-arm-eabi":
+ optional: true
+ "@next/swc-android-arm64":
+ optional: true
+ "@next/swc-darwin-arm64":
+ optional: true
+ "@next/swc-darwin-x64":
+ optional: true
+ "@next/swc-freebsd-x64":
+ optional: true
+ "@next/swc-linux-arm-gnueabihf":
+ 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:
+ fibers:
+ optional: true
+ node-sass:
+ optional: true
+ sass:
+ optional: true
+ bin:
+ next: dist/bin/next
+ checksum: d96fc4f5bcd5a630d74111519f4820dcbd75dddf16c6d00d2167bd3cb8d74965d46d83c8e5ec301bf999013c7d96f1bfff9424f0221317d68b594c4d01f5825e
+ languageName: node
+ linkType: hard
+
"next@npm:^13.2.1":
version: 13.2.3
resolution: "next@npm:13.2.3"
@@ -30037,13 +30961,6 @@ __metadata:
languageName: node
linkType: hard
-"oauth4webapi@npm:2.0.5":
- version: 2.0.5
- resolution: "oauth4webapi@npm:2.0.5"
- checksum: 32d0cb7b1cca42d51dfb88075ca2d69fe33172a807e8ea50e317d17cab3bc80588ab8ebcb7eb4600c371a70af4674595b4b341daf6f3a655f1efa1ab715bb6c9
- languageName: node
- linkType: hard
-
"oauth@npm:^0.9.15":
version: 0.9.15
resolution: "oauth@npm:0.9.15"
@@ -30104,6 +31021,24 @@ __metadata:
languageName: node
linkType: hard
+"object-keys@npm:~0.2.0":
+ version: 0.2.0
+ resolution: "object-keys@npm:0.2.0"
+ dependencies:
+ foreach: ~2.0.1
+ indexof: ~0.0.1
+ is: ~0.2.6
+ checksum: 4b96bab88fe9df22a03aec3c59a084bdffc789ad1318a39081e6b8389af6b9ab8571dd3776eed3ec5831137d057fb7ba76911552c6a6efd59b5d126ac3b6e432
+ languageName: node
+ linkType: hard
+
+"object-keys@npm:~0.4.0":
+ version: 0.4.0
+ resolution: "object-keys@npm:0.4.0"
+ checksum: 1be3ebe9b48c0d5eda8e4a30657d887a748cb42435e0e2eaf49faf557bdd602cd2b7558b8ce90a4eb2b8592d16b875a1900bce859cbb0f35b21c67e11a45313c
+ languageName: node
+ linkType: hard
+
"object-path@npm:^0.11.8":
version: 0.11.8
resolution: "object-path@npm:0.11.8"
@@ -30267,6 +31202,13 @@ __metadata:
languageName: node
linkType: hard
+"octal@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "octal@npm:1.0.0"
+ checksum: d648917f4f0a1042d7a4e230262aed00274c9791fe4795e9a2ce3b64ab7f2ca93e62cd55ca5ad4e4bd3fc375ca84d6919d7bf417be461790c1042503ac2c2310
+ languageName: node
+ linkType: hard
+
"oidc-token-hash@npm:^5.0.1":
version: 5.0.1
resolution: "oidc-token-hash@npm:5.0.1"
@@ -31709,17 +32651,6 @@ __metadata:
languageName: node
linkType: hard
-"preact-render-to-string@npm:5.2.3":
- version: 5.2.3
- resolution: "preact-render-to-string@npm:5.2.3"
- dependencies:
- pretty-format: ^3.8.0
- peerDependencies:
- preact: ">=10"
- checksum: 6e46288d8956adde35b9fe3a21aecd9dea29751b40f0f155dea62f3896f27cb8614d457b32f48d33909d2da81135afcca6c55077520feacd7d15164d1371fb44
- languageName: node
- linkType: hard
-
"preact-render-to-string@npm:^5.1.19":
version: 5.2.6
resolution: "preact-render-to-string@npm:5.2.6"
@@ -31731,13 +32662,6 @@ __metadata:
languageName: node
linkType: hard
-"preact@npm:10.11.3, preact@npm:^10.6.3":
- version: 10.11.3
- resolution: "preact@npm:10.11.3"
- checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367
- languageName: node
- linkType: hard
-
"preact@npm:10.4.1":
version: 10.4.1
resolution: "preact@npm:10.4.1"
@@ -31752,6 +32676,13 @@ __metadata:
languageName: node
linkType: hard
+"preact@npm:^10.6.3":
+ version: 10.11.3
+ resolution: "preact@npm:10.11.3"
+ checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367
+ languageName: node
+ linkType: hard
+
"prelude-ls@npm:^1.2.1":
version: 1.2.1
resolution: "prelude-ls@npm:1.2.1"
@@ -31976,6 +32907,18 @@ __metadata:
languageName: node
linkType: hard
+"prisma@npm:^4.7.1":
+ version: 4.12.0
+ resolution: "prisma@npm:4.12.0"
+ dependencies:
+ "@prisma/engines": 4.12.0
+ bin:
+ prisma: build/index.js
+ prisma2: build/index.js
+ checksum: 826b90901391eead0aa2e1ab4539474c308f8a956b480e87594b241bfaba423ebd781b8082fc329f4e7e780f3a7162a09ac61c367d3f43382e93d506e4a66c7c
+ languageName: node
+ linkType: hard
+
"prismjs@npm:^1.27.0":
version: 1.28.0
resolution: "prismjs@npm:1.28.0"
@@ -31990,6 +32933,13 @@ __metadata:
languageName: node
linkType: hard
+"process-es6@npm:^0.11.2":
+ version: 0.11.6
+ resolution: "process-es6@npm:0.11.6"
+ checksum: 8849ea1a799a20a8e863fd3a5558d4085357ee59cae16b76f61327e3b3a27697b4e49c880742a6cc0f0c37eb0bd78fb0d4e382bd2e5318bb699b404b55a9b91e
+ languageName: node
+ linkType: hard
+
"process-nextick-args@npm:~2.0.0":
version: 2.0.1
resolution: "process-nextick-args@npm:2.0.1"
@@ -32130,6 +33080,13 @@ __metadata:
languageName: node
linkType: hard
+"prr@npm:~0.0.0":
+ version: 0.0.0
+ resolution: "prr@npm:0.0.0"
+ checksum: 6552d9d92d9d55ec1afb8952ad80f81bbb1b4379f24ff7c506ad083ea701caf1bf6d4b092a2baeb98ec3f312c5a49d8bdf1d9b20a6db2998d05c2d52aa6a82e7
+ languageName: node
+ linkType: hard
+
"prr@npm:~1.0.1":
version: 1.0.1
resolution: "prr@npm:1.0.1"
@@ -32768,6 +33725,17 @@ __metadata:
languageName: node
linkType: hard
+"react-error-boundary@npm:^3.1.0":
+ version: 3.1.4
+ resolution: "react-error-boundary@npm:3.1.4"
+ dependencies:
+ "@babel/runtime": ^7.12.5
+ peerDependencies:
+ react: ">=16.13.1"
+ checksum: f36270a5d775a25c8920f854c0d91649ceea417b15b5bc51e270a959b0476647bb79abb4da3be7dd9a4597b029214e8fe43ea914a7f16fa7543c91f784977f1b
+ languageName: node
+ linkType: hard
+
"react-fast-marquee@npm:^1.3.5":
version: 1.3.5
resolution: "react-fast-marquee@npm:1.3.5"
@@ -32814,6 +33782,15 @@ __metadata:
languageName: node
linkType: hard
+"react-hook-form@npm:^7.34.2":
+ version: 7.43.9
+ resolution: "react-hook-form@npm:7.43.9"
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18
+ checksum: 65b94de625f2b7921c4e856bf0abbe142bfe06c052217bd1bcc3a842e2cc37fa3a3e03758119dc038bbcf5edb49e02c29206528b80b201f9a4d601471ef78153
+ languageName: node
+ linkType: hard
+
"react-hook-form@npm:^7.43.3":
version: 7.43.3
resolution: "react-hook-form@npm:7.43.3"
@@ -33240,6 +34217,15 @@ __metadata:
languageName: node
linkType: hard
+"react-sticky-box@npm:^2.0.4":
+ version: 2.0.4
+ resolution: "react-sticky-box@npm:2.0.4"
+ peerDependencies:
+ react: ">=16.8.0"
+ checksum: 6f6c1ab7b04851bcafce12cf9125d60d7944ff4f713ecfa53babb7695c6bfbb66d89a3e3cb040145a895a49a0fc9d47cf7325de3402a95f7fb16899ea96f2f80
+ languageName: node
+ linkType: hard
+
"react-string-replace@npm:^1.1.0":
version: 1.1.0
resolution: "react-string-replace@npm:1.1.0"
@@ -33490,6 +34476,18 @@ __metadata:
languageName: node
linkType: hard
+"readable-stream@npm:^1.0.26-4":
+ version: 1.1.14
+ resolution: "readable-stream@npm:1.1.14"
+ dependencies:
+ core-util-is: ~1.0.0
+ inherits: ~2.0.1
+ isarray: 0.0.1
+ string_decoder: ~0.10.x
+ checksum: 17dfeae3e909945a4a1abc5613ea92d03269ef54c49288599507fc98ff4615988a1c39a999dcf9aacba70233d9b7040bc11a5f2bfc947e262dedcc0a8b32b5a0
+ languageName: node
+ linkType: hard
+
"readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0":
version: 3.6.0
resolution: "readable-stream@npm:3.6.0"
@@ -33501,6 +34499,18 @@ __metadata:
languageName: node
linkType: hard
+"readable-stream@npm:~1.0.26, readable-stream@npm:~1.0.26-4":
+ version: 1.0.34
+ resolution: "readable-stream@npm:1.0.34"
+ dependencies:
+ core-util-is: ~1.0.0
+ inherits: ~2.0.1
+ isarray: 0.0.1
+ string_decoder: ~0.10.x
+ checksum: 85042c537e4f067daa1448a7e257a201070bfec3dd2706abdbd8ebc7f3418eb4d3ed4b8e5af63e2544d69f88ab09c28d5da3c0b77dc76185fddd189a59863b60
+ languageName: node
+ linkType: hard
+
"readdirp@npm:^2.2.1":
version: 2.2.1
resolution: "readdirp@npm:2.2.1"
@@ -34348,6 +35358,18 @@ __metadata:
languageName: node
linkType: hard
+"rollup-plugin-node-builtins@npm:^2.1.2":
+ version: 2.1.2
+ resolution: "rollup-plugin-node-builtins@npm:2.1.2"
+ dependencies:
+ browserify-fs: ^1.0.0
+ buffer-es6: ^4.9.2
+ crypto-browserify: ^3.11.0
+ process-es6: ^0.11.2
+ checksum: 184338123fff678e1671ef958621058b679b5bc955620bb4457fe7e7005c6550497b9d978f7f74705ea46a6adedaa541066d3c7454c6878bde48791cd9a7b61a
+ languageName: node
+ linkType: hard
+
"rollup-plugin-polyfill-node@npm:^0.10.2":
version: 0.10.2
resolution: "rollup-plugin-polyfill-node@npm:0.10.2"
@@ -34387,6 +35409,20 @@ __metadata:
languageName: node
linkType: hard
+"rollup@npm:^2.79.1":
+ version: 2.79.1
+ resolution: "rollup@npm:2.79.1"
+ dependencies:
+ fsevents: ~2.3.2
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ bin:
+ rollup: dist/bin/rollup
+ checksum: 6a2bf167b3587d4df709b37d149ad0300692cc5deb510f89ac7bdc77c8738c9546ae3de9322b0968e1ed2b0e984571f5f55aae28fa7de4cfcb1bc5402a4e2be6
+ languageName: node
+ linkType: hard
+
"rollup@npm:^3.10.0":
version: 3.17.0
resolution: "rollup@npm:3.17.0"
@@ -34870,6 +35906,15 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:~2.3.1":
+ version: 2.3.2
+ resolution: "semver@npm:2.3.2"
+ bin:
+ semver: ./bin/semver
+ checksum: e0649fb18a1da909df7b5a6f586314a7f6e052385fc1e6eafa7084dd77c0787e755ab35ca491f9eec986fe1d0d6d36eae85a21eb7e2ed32ae5906796acb92c56
+ languageName: node
+ linkType: hard
+
"send@npm:0.17.2":
version: 0.17.2
resolution: "send@npm:0.17.2"
@@ -35911,6 +36956,13 @@ __metadata:
languageName: node
linkType: hard
+"string-range@npm:~1.2, string-range@npm:~1.2.1":
+ version: 1.2.2
+ resolution: "string-range@npm:1.2.2"
+ checksum: 7118cc83a7e63fca5fd8bef9b61464bfc51197b5f6dc475c9e1d24a93ce02fa27f7adb4cd7adac5daf599bde442b383608078f9b051bddb108d3b45840923097
+ languageName: node
+ linkType: hard
+
"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3":
version: 4.2.3
resolution: "string-width@npm:4.2.3"
@@ -36056,6 +37108,13 @@ __metadata:
languageName: node
linkType: hard
+"string_decoder@npm:~0.10.x":
+ version: 0.10.31
+ resolution: "string_decoder@npm:0.10.31"
+ checksum: fe00f8e303647e5db919948ccb5ce0da7dea209ab54702894dd0c664edd98e5d4df4b80d6fabf7b9e92b237359d21136c95bf068b2f7760b772ca974ba970202
+ languageName: node
+ linkType: hard
+
"string_decoder@npm:~1.1.1":
version: 1.1.1
resolution: "string_decoder@npm:1.1.1"
@@ -36305,6 +37364,20 @@ __metadata:
languageName: node
linkType: hard
+"styled-jsx@npm:5.0.7":
+ version: 5.0.7
+ resolution: "styled-jsx@npm:5.0.7"
+ peerDependencies:
+ react: ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+ peerDependenciesMeta:
+ "@babel/core":
+ optional: true
+ babel-plugin-macros:
+ optional: true
+ checksum: 61959993915f4b1662a682dbbefb3512de9399cf6901969bcadd26ba5441d2b5ca5c1021b233bbd573da2541b41efb45d56c6f618dbc8d88a381ebc62461fefe
+ languageName: node
+ linkType: hard
+
"styled-jsx@npm:5.1.1":
version: 5.1.1
resolution: "styled-jsx@npm:5.1.1"
@@ -37683,6 +38756,13 @@ __metadata:
languageName: node
linkType: hard
+"turbo-darwin-64@npm:1.9.3":
+ version: 1.9.3
+ resolution: "turbo-darwin-64@npm:1.9.3"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"turbo-darwin-arm64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-darwin-arm64@npm:1.8.3"
@@ -37690,6 +38770,13 @@ __metadata:
languageName: node
linkType: hard
+"turbo-darwin-arm64@npm:1.9.3":
+ version: 1.9.3
+ resolution: "turbo-darwin-arm64@npm:1.9.3"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"turbo-linux-64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-linux-64@npm:1.8.3"
@@ -37697,6 +38784,13 @@ __metadata:
languageName: node
linkType: hard
+"turbo-linux-64@npm:1.9.3":
+ version: 1.9.3
+ resolution: "turbo-linux-64@npm:1.9.3"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"turbo-linux-arm64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-linux-arm64@npm:1.8.3"
@@ -37704,6 +38798,13 @@ __metadata:
languageName: node
linkType: hard
+"turbo-linux-arm64@npm:1.9.3":
+ version: 1.9.3
+ resolution: "turbo-linux-arm64@npm:1.9.3"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"turbo-windows-64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-windows-64@npm:1.8.3"
@@ -37711,6 +38812,13 @@ __metadata:
languageName: node
linkType: hard
+"turbo-windows-64@npm:1.9.3":
+ version: 1.9.3
+ resolution: "turbo-windows-64@npm:1.9.3"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"turbo-windows-arm64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-windows-arm64@npm:1.8.3"
@@ -37718,6 +38826,42 @@ __metadata:
languageName: node
linkType: hard
+"turbo-windows-arm64@npm:1.9.3":
+ version: 1.9.3
+ resolution: "turbo-windows-arm64@npm:1.9.3"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"turbo@npm:^1.4.3":
+ version: 1.9.3
+ resolution: "turbo@npm:1.9.3"
+ dependencies:
+ turbo-darwin-64: 1.9.3
+ turbo-darwin-arm64: 1.9.3
+ turbo-linux-64: 1.9.3
+ turbo-linux-arm64: 1.9.3
+ turbo-windows-64: 1.9.3
+ turbo-windows-arm64: 1.9.3
+ dependenciesMeta:
+ turbo-darwin-64:
+ optional: true
+ turbo-darwin-arm64:
+ optional: true
+ turbo-linux-64:
+ optional: true
+ turbo-linux-arm64:
+ optional: true
+ turbo-windows-64:
+ optional: true
+ turbo-windows-arm64:
+ optional: true
+ bin:
+ turbo: bin/turbo
+ checksum: ebf06d3b9b1401a5baabace238cd1e0d8fc1dc062b4f7bd577f644298c555f326d15f331144641c0b43a60ae8058769bcbd9d1660874fa9927ec64b5be8ee9dc
+ languageName: node
+ linkType: hard
+
"turbo@npm:^1.8.3":
version: 1.8.3
resolution: "turbo@npm:1.8.3"
@@ -37943,6 +39087,13 @@ __metadata:
languageName: node
linkType: hard
+"typedarray-to-buffer@npm:~1.0.0":
+ version: 1.0.4
+ resolution: "typedarray-to-buffer@npm:1.0.4"
+ checksum: ac6989c456a0b175c8362b3ebbd8a74af7b9bcc94f9dc9ffd34436569cd29aea6a1e0e5f5752d0d5bd855a55b2520e960d1d4cb9c9149f863ce09220540df17f
+ languageName: node
+ linkType: hard
+
"typedarray@npm:^0.0.6":
version: 0.0.6
resolution: "typedarray@npm:0.0.6"
@@ -38032,6 +39183,16 @@ __metadata:
languageName: node
linkType: hard
+"typescript@npm:^4.7.4, typescript@npm:^4.9.3":
+ version: 4.9.5
+ resolution: "typescript@npm:4.9.5"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db
+ languageName: node
+ linkType: hard
+
"typescript@npm:^4.9.4":
version: 4.9.4
resolution: "typescript@npm:4.9.4"
@@ -38042,6 +39203,16 @@ __metadata:
languageName: node
linkType: hard
+"typescript@patch:typescript@^4.7.4#~builtin, typescript@patch:typescript@^4.9.3#~builtin":
+ version: 4.9.5
+ resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=23ec76"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: ab417a2f398380c90a6cf5a5f74badd17866adf57f1165617d6a551f059c3ba0a3e4da0d147b3ac5681db9ac76a303c5876394b13b3de75fdd5b1eaa06181c9d
+ languageName: node
+ linkType: hard
+
"typescript@patch:typescript@^4.9.4#~builtin":
version: 4.9.4
resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin::version=4.9.4&hash=23ec76"
@@ -39089,6 +40260,44 @@ __metadata:
languageName: node
linkType: hard
+"vite@npm:^3.2.4":
+ version: 3.2.5
+ resolution: "vite@npm:3.2.5"
+ dependencies:
+ esbuild: ^0.15.9
+ fsevents: ~2.3.2
+ postcss: ^8.4.18
+ resolve: ^1.22.1
+ rollup: ^2.79.1
+ peerDependencies:
+ "@types/node": ">= 14"
+ less: "*"
+ sass: "*"
+ stylus: "*"
+ sugarss: "*"
+ terser: ^5.4.0
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ bin:
+ vite: bin/vite.js
+ checksum: ad35b7008c2b62a167d1d1a82f0a0c60fa457733f1969e9eedf0b0077f67a7ac74b4c9477e75a397895150f09b6551f0c17841c5b05c34d9fe302bb0b5dc28a8
+ languageName: node
+ linkType: hard
+
"vite@npm:^4.1.2":
version: 4.1.2
resolution: "vite@npm:4.1.2"
@@ -40339,6 +41548,13 @@ __metadata:
languageName: node
linkType: hard
+"xtend@npm:^2.2.0":
+ version: 2.2.0
+ resolution: "xtend@npm:2.2.0"
+ checksum: 9fcd1ddabefdb3c68a698b08177525ad14a6df3423b13bad9a53900d19374e476a43c219b0756d39675776b2326a35fe477c547cfb8a05ae9fea4ba2235bebe2
+ languageName: node
+ linkType: hard
+
"xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:^4.0.2, xtend@npm:~4.0.1":
version: 4.0.2
resolution: "xtend@npm:4.0.2"
@@ -40346,6 +41562,32 @@ __metadata:
languageName: node
linkType: hard
+"xtend@npm:~2.0.4":
+ version: 2.0.6
+ resolution: "xtend@npm:2.0.6"
+ dependencies:
+ is-object: ~0.1.2
+ object-keys: ~0.2.0
+ checksum: 414531e51cbc56d4676ae2b3a4070052e0c7a36caf7ee74f2e8449fe0fc1752b971a776fca5b85ec02ef3d0a33b8e75491d900474b8407f3f4bba3f49325a785
+ languageName: node
+ linkType: hard
+
+"xtend@npm:~2.1.2":
+ version: 2.1.2
+ resolution: "xtend@npm:2.1.2"
+ dependencies:
+ object-keys: ~0.4.0
+ checksum: a8b79f31502c163205984eaa2b196051cd2fab0882b49758e30f2f9018255bc6c462e32a090bf3385d1bda04755ad8cc0052a09e049b0038f49eb9b950d9c447
+ languageName: node
+ linkType: hard
+
+"xtend@npm:~3.0.0":
+ version: 3.0.0
+ resolution: "xtend@npm:3.0.0"
+ checksum: ecdc4dd74f26e561dbc13d4148fcc7b8f46f49b9259862fc31e42b7cede9eee62af9d869050a7b8e089475e858744a74ceae3f0da2943755ef712f3277ad2e50
+ languageName: node
+ linkType: hard
+
"y18n@npm:^4.0.0":
version: 4.0.3
resolution: "y18n@npm:4.0.3"
@@ -40631,7 +41873,7 @@ __metadata:
languageName: node
linkType: hard
-"zod@npm:^3.17.3":
+"zod@npm:^3.17.3, zod@npm:^3.19.1":
version: 3.21.4
resolution: "zod@npm:3.21.4"
checksum: f185ba87342ff16f7a06686767c2b2a7af41110c7edf7c1974095d8db7a73792696bcb4a00853de0d2edeb34a5b2ea6a55871bc864227dace682a0a28de33e1f
@@ -40662,9 +41904,9 @@ __metadata:
languageName: node
linkType: hard
-"zustand@npm:^4.1.4":
- version: 4.1.5
- resolution: "zustand@npm:4.1.5"
+"zustand@npm:^4.3.2":
+ version: 4.3.6
+ resolution: "zustand@npm:4.3.6"
dependencies:
use-sync-external-store: 1.2.0
peerDependencies:
@@ -40675,7 +41917,7 @@ __metadata:
optional: true
react:
optional: true
- checksum: 13190ee8e8a797c5347b525a7c392be62b2addacdd9645dd20d37ea053f96c7c7067c099c6201e98ebb8d54991f2e04e241cc323f9a25b841d44f0ae048e3afc
+ checksum: 4d3cec03526f04ff3de6dc45b6f038c47f091836af9660fbf5f682cae1628221102882df20e4048dfe699a43f67424e5d6afc1116f3838a80eea5dd4f95ddaed
languageName: node
linkType: hard