diff --git a/.env.example b/.env.example index 283059571c..7f7e020437 100644 --- a/.env.example +++ b/.env.example @@ -348,6 +348,7 @@ APP_ROUTER_GETTING_STARTED_STEP_ENABLED=0 APP_ROUTER_APPS_ENABLED=0 APP_ROUTER_VIDEO_ENABLED=0 APP_ROUTER_TEAMS_ENABLED=0 +APP_ROUTER_AVAILABILITY_ENABLED=0 APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0 APP_ROUTER_AUTH_LOGIN_ENABLED=0 APP_ROUTER_AUTH_LOGOUT_ENABLED=0 diff --git a/apps/web/abTest/middlewareFactory.ts b/apps/web/abTest/middlewareFactory.ts index eb1f61be51..7122d67318 100644 --- a/apps/web/abTest/middlewareFactory.ts +++ b/apps/web/abTest/middlewareFactory.ts @@ -28,6 +28,7 @@ const ROUTES: [URLPattern, boolean][] = [ ["/bookings/:status", process.env.APP_ROUTER_BOOKINGS_STATUS_ENABLED === "1"] as const, ["/video/:path*", process.env.APP_ROUTER_VIDEO_ENABLED === "1"] as const, ["/teams", process.env.APP_ROUTER_TEAMS_ENABLED === "1"] as const, + ["/availability", process.env.APP_ROUTER_AVAILABILITY_ENABLED === "1"] as const, ].map(([pathname, enabled]) => [ new URLPattern({ pathname, diff --git a/apps/web/app/future/availability/[schedule]/page.tsx b/apps/web/app/future/availability/[schedule]/page.tsx new file mode 100644 index 0000000000..96c99517ab --- /dev/null +++ b/apps/web/app/future/availability/[schedule]/page.tsx @@ -0,0 +1,26 @@ +import type { PageProps } from "app/_types"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import { ScheduleRepository } from "@calcom/lib/server/repository/schedule"; + +import { AvailabilitySettingsWebWrapper } from "~/availability/[schedule]/schedule-view"; + +export const generateMetadata = async ({ params }: PageProps) => { + const scheduleId = params?.schedule ? Number(params.schedule) : -1; + const schedule = await ScheduleRepository.findScheduleById({ id: scheduleId }); + + if (!schedule) { + return await _generateMetadata( + (t) => t("availability"), + () => "" + ); + } + + return await _generateMetadata( + (t) => (schedule.name ? `${schedule.name} | ${t("availability")}` : t("availability")), + () => "" + ); +}; + +export default WithLayout({ getLayout: null, Page: AvailabilitySettingsWebWrapper }); diff --git a/apps/web/app/future/availability/page.tsx b/apps/web/app/future/availability/page.tsx new file mode 100644 index 0000000000..f793a90fec --- /dev/null +++ b/apps/web/app/future/availability/page.tsx @@ -0,0 +1,13 @@ +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import AvailabilityPage from "~/availability/availability-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => t("availability"), + (t) => t("configure_availability") + ); +}; + +export default WithLayout({ getLayout: null, Page: AvailabilityPage }); diff --git a/apps/web/app/future/availability/troubleshoot/page.tsx b/apps/web/app/future/availability/troubleshoot/page.tsx new file mode 100644 index 0000000000..7ea6a6d97d --- /dev/null +++ b/apps/web/app/future/availability/troubleshoot/page.tsx @@ -0,0 +1,13 @@ +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import Troubleshoot, { getLayout } from "~/availability/troubleshoot/troubleshoot-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + (t) => t("troubleshoot"), + (t) => t("troubleshoot_availability") + ); +}; + +export default WithLayout({ getLayout, Page: Troubleshoot }); diff --git a/packages/platform/atoms/availability/wrappers/AvailabilitySettingsWebWrapper.tsx b/apps/web/modules/availability/[schedule]/schedule-view.tsx similarity index 96% rename from packages/platform/atoms/availability/wrappers/AvailabilitySettingsWebWrapper.tsx rename to apps/web/modules/availability/[schedule]/schedule-view.tsx index 37a54cbc93..03fc0108c3 100644 --- a/packages/platform/atoms/availability/wrappers/AvailabilitySettingsWebWrapper.tsx +++ b/apps/web/modules/availability/[schedule]/schedule-view.tsx @@ -1,6 +1,9 @@ +"use client"; + import { useRouter } from "next/navigation"; import { useState } from "react"; +import { AvailabilitySettings } from "@calcom/atoms/monorepo"; import { withErrorFromUnknown } from "@calcom/lib/getClientErrorFromUnknown"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -9,8 +12,6 @@ import { trpc } from "@calcom/trpc/react"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import { showToast } from "@calcom/ui"; -import { AvailabilitySettings } from "../AvailabilitySettings"; - export const AvailabilitySettingsWebWrapper = () => { const searchParams = useCompatSearchParams(); const { t } = useLocale(); @@ -27,8 +28,7 @@ export const AvailabilitySettingsWebWrapper = () => { } ); - const { data: travelSchedules, isPending: isPendingTravelSchedules } = - trpc.viewer.getTravelSchedules.useQuery(); + const { data: travelSchedules } = trpc.viewer.getTravelSchedules.useQuery(); const [isBulkUpdateModalOpen, setIsBulkUpdateModalOpen] = useState(false); const bulkUpdateDefaultAvailabilityMutation = diff --git a/apps/web/modules/availability/availability-view.tsx b/apps/web/modules/availability/availability-view.tsx new file mode 100644 index 0000000000..378ed5c28a --- /dev/null +++ b/apps/web/modules/availability/availability-view.tsx @@ -0,0 +1,233 @@ +"use client"; + +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import Link from "next/link"; +import { useRouter, usePathname } from "next/navigation"; +import { useCallback, useState } from "react"; + +import SkeletonLoader from "@calcom/features/availability/components/SkeletonLoader"; +import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; +import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules"; +import Shell from "@calcom/features/shell/Shell"; +import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { HttpError } from "@calcom/lib/http-error"; +import { MembershipRole } from "@calcom/prisma/enums"; +import type { RouterOutputs } from "@calcom/trpc/react"; +import { trpc } from "@calcom/trpc/react"; +import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; +import { EmptyScreen, showToast, ToggleGroup } from "@calcom/ui"; + +import { QueryCell } from "@lib/QueryCell"; + +export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) { + const { t } = useLocale(); + const [bulkUpdateModal, setBulkUpdateModal] = useState(false); + const utils = trpc.useUtils(); + + const meQuery = trpc.viewer.me.useQuery(); + + const router = useRouter(); + + const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({ + onMutate: async ({ scheduleId }) => { + await utils.viewer.availability.list.cancel(); + const previousValue = utils.viewer.availability.list.getData(); + if (previousValue) { + const filteredValue = previousValue.schedules.filter(({ id }) => id !== scheduleId); + utils.viewer.availability.list.setData(undefined, { ...previousValue, schedules: filteredValue }); + } + + return { previousValue }; + }, + + onError: (err, variables, context) => { + if (context?.previousValue) { + utils.viewer.availability.list.setData(undefined, context.previousValue); + } + if (err instanceof HttpError) { + const message = `${err.statusCode}: ${err.message}`; + showToast(message, "error"); + } + }, + onSettled: () => { + utils.viewer.availability.list.invalidate(); + }, + onSuccess: () => { + showToast(t("schedule_deleted_successfully"), "success"); + }, + }); + + const updateMutation = trpc.viewer.availability.schedule.update.useMutation({ + onSuccess: async ({ schedule }) => { + await utils.viewer.availability.list.invalidate(); + showToast( + t("availability_updated_successfully", { + scheduleName: schedule.name, + }), + "success" + ); + setBulkUpdateModal(true); + }, + onError: (err) => { + if (err instanceof HttpError) { + const message = `${err.statusCode}: ${err.message}`; + showToast(message, "error"); + } + }, + }); + + const bulkUpdateDefaultAvailabilityMutation = + trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation({ + onSuccess: () => { + utils.viewer.availability.list.invalidate(); + setBulkUpdateModal(false); + showToast(t("success"), "success"); + }, + }); + + const duplicateMutation = trpc.viewer.availability.schedule.duplicate.useMutation({ + onSuccess: async ({ schedule }) => { + await router.push(`/availability/${schedule.id}`); + showToast(t("schedule_created_successfully", { scheduleName: schedule.name }), "success"); + }, + onError: (err) => { + if (err instanceof HttpError) { + const message = `${err.statusCode}: ${err.message}`; + showToast(message, "error"); + } + }, + }); + + // Adds smooth delete button - item fades and old item slides into place + + const [animationParentRef] = useAutoAnimate(); + + return ( + <> + {schedules.length === 0 ? ( +
+ } + /> +
+ ) : ( + <> +
+ +
+
+ {t("temporarily_out_of_office")}{" "} + + {t("add_a_redirect")} + +
+ {bulkUpdateModal && ( + + )} + + )} + + ); +} + +function AvailabilityListWithQuery() { + const query = trpc.viewer.availability.list.useQuery(); + + return ( + } + customLoader={} + /> + ); +} + +export default function AvailabilityPage() { + const { t } = useLocale(); + const searchParams = useCompatSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + const me = useMeQuery(); + const { data } = trpc.viewer.organizations.listCurrent.useQuery(); + + // Get a new searchParams string by merging the current + // searchParams with a provided key/value pair + const createQueryString = useCallback( + (name: string, value: string) => { + const params = new URLSearchParams(searchParams ?? undefined); + params.set(name, value); + + return params.toString(); + }, + [searchParams] + ); + + const isOrgAdminOrOwner = + data && (data.user.role === MembershipRole.OWNER || data.user.role === MembershipRole.ADMIN); + const isOrgAndPrivate = data?.isOrganization && data.isPrivate; + const toggleGroupOptions = [{ value: "mine", label: t("my_availability") }]; + + if (!isOrgAndPrivate || isOrgAdminOrOwner) { + toggleGroupOptions.push({ value: "team", label: t("team_availability") }); + } + + return ( +
+ + { + if (!value) return; + router.push(`${pathname}?${createQueryString("type", value)}`); + }} + options={toggleGroupOptions} + /> + +
+ }> + {searchParams?.get("type") === "team" && (!isOrgAndPrivate || isOrgAdminOrOwner) ? ( + + ) : ( + + )} + + + ); +} diff --git a/apps/web/modules/availability/troubleshoot/troubleshoot-view.tsx b/apps/web/modules/availability/troubleshoot/troubleshoot-view.tsx new file mode 100644 index 0000000000..d5dab8c083 --- /dev/null +++ b/apps/web/modules/availability/troubleshoot/troubleshoot-view.tsx @@ -0,0 +1,34 @@ +"use client"; + +import dynamic from "next/dynamic"; +import { Suspense } from "react"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { HeadSeo, Loader } from "@calcom/ui"; + +const TroubleshooterClientOnly = dynamic( + () => import("@calcom/features/troubleshooter/Troubleshooter").then((mod) => mod.Troubleshooter), + { + ssr: false, + } +); + +function TroubleshooterPage() { + const { t } = useLocale(); + return ( + <> + + + + + }> + + + + ); +} +export { getLayout } from "@calcom/features/troubleshooter/layout"; + +export default TroubleshooterPage; diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx index 5629c0c701..2c95f64f5f 100644 --- a/apps/web/pages/availability/[schedule].tsx +++ b/apps/web/pages/availability/[schedule].tsx @@ -1,9 +1,9 @@ -import { AvailabilitySettings } from "@calcom/atoms/monorepo"; - import PageWrapper from "@components/PageWrapper"; -export default function Availability() { - return ; -} +import { AvailabilitySettingsWebWrapper } from "~/availability/[schedule]/schedule-view"; -Availability.PageWrapper = PageWrapper; +const AvailabilitySettings = () => ; + +AvailabilitySettings.PageWrapper = PageWrapper; + +export default AvailabilitySettings; diff --git a/apps/web/pages/availability/index.tsx b/apps/web/pages/availability/index.tsx index 8ca3eb6ec2..fd2aeffe88 100644 --- a/apps/web/pages/availability/index.tsx +++ b/apps/web/pages/availability/index.tsx @@ -1,235 +1,9 @@ -import { useAutoAnimate } from "@formkit/auto-animate/react"; -import Link from "next/link"; -import { useRouter, usePathname } from "next/navigation"; -import { useCallback, useState } from "react"; - -import SkeletonLoader from "@calcom/features/availability/components/SkeletonLoader"; -import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal"; -import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules"; -import Shell from "@calcom/features/shell/Shell"; -import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable"; -import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { HttpError } from "@calcom/lib/http-error"; -import { MembershipRole } from "@calcom/prisma/enums"; -import type { RouterOutputs } from "@calcom/trpc/react"; -import { trpc } from "@calcom/trpc/react"; -import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; -import { EmptyScreen, showToast, ToggleGroup } from "@calcom/ui"; - -import { QueryCell } from "@lib/QueryCell"; - import PageWrapper from "@components/PageWrapper"; -export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) { - const { t } = useLocale(); - const [bulkUpdateModal, setBulkUpdateModal] = useState(false); - const utils = trpc.useUtils(); +import _AvailabilityPage from "~/availability/availability-view"; - const meQuery = trpc.viewer.me.useQuery(); - - const router = useRouter(); - - const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({ - onMutate: async ({ scheduleId }) => { - await utils.viewer.availability.list.cancel(); - const previousValue = utils.viewer.availability.list.getData(); - if (previousValue) { - const filteredValue = previousValue.schedules.filter(({ id }) => id !== scheduleId); - utils.viewer.availability.list.setData(undefined, { ...previousValue, schedules: filteredValue }); - } - - return { previousValue }; - }, - - onError: (err, variables, context) => { - if (context?.previousValue) { - utils.viewer.availability.list.setData(undefined, context.previousValue); - } - if (err instanceof HttpError) { - const message = `${err.statusCode}: ${err.message}`; - showToast(message, "error"); - } - }, - onSettled: () => { - utils.viewer.availability.list.invalidate(); - }, - onSuccess: () => { - showToast(t("schedule_deleted_successfully"), "success"); - }, - }); - - const updateMutation = trpc.viewer.availability.schedule.update.useMutation({ - onSuccess: async ({ schedule }) => { - await utils.viewer.availability.list.invalidate(); - showToast( - t("availability_updated_successfully", { - scheduleName: schedule.name, - }), - "success" - ); - setBulkUpdateModal(true); - }, - onError: (err) => { - if (err instanceof HttpError) { - const message = `${err.statusCode}: ${err.message}`; - showToast(message, "error"); - } - }, - }); - - const bulkUpdateDefaultAvailabilityMutation = - trpc.viewer.availability.schedule.bulkUpdateToDefaultAvailability.useMutation({ - onSuccess: () => { - utils.viewer.availability.list.invalidate(); - setBulkUpdateModal(false); - showToast(t("success"), "success"); - }, - }); - - const duplicateMutation = trpc.viewer.availability.schedule.duplicate.useMutation({ - onSuccess: async ({ schedule }) => { - await router.push(`/availability/${schedule.id}`); - showToast(t("schedule_created_successfully", { scheduleName: schedule.name }), "success"); - }, - onError: (err) => { - if (err instanceof HttpError) { - const message = `${err.statusCode}: ${err.message}`; - showToast(message, "error"); - } - }, - }); - - // Adds smooth delete button - item fades and old item slides into place - - const [animationParentRef] = useAutoAnimate(); - - return ( - <> - {schedules.length === 0 ? ( -
- } - /> -
- ) : ( - <> -
-
    - {schedules.map((schedule) => ( - - ))} -
-
-
- {t("temporarily_out_of_office")}{" "} - - {t("add_a_redirect")} - -
- {bulkUpdateModal && ( - - )} - - )} - - ); -} - -function AvailabilityListWithQuery() { - const query = trpc.viewer.availability.list.useQuery(); - - return ( - } - customLoader={} - /> - ); -} - -export default function AvailabilityPage() { - const { t } = useLocale(); - const searchParams = useCompatSearchParams(); - const router = useRouter(); - const pathname = usePathname(); - const me = useMeQuery(); - const { data } = trpc.viewer.organizations.listCurrent.useQuery(); - - // Get a new searchParams string by merging the current - // searchParams with a provided key/value pair - const createQueryString = useCallback( - (name: string, value: string) => { - const params = new URLSearchParams(searchParams ?? undefined); - params.set(name, value); - - return params.toString(); - }, - [searchParams] - ); - - const isOrgAdminOrOwner = - data && (data.user.role === MembershipRole.OWNER || data.user.role === MembershipRole.ADMIN); - const isOrgAndPrivate = data?.isOrganization && data.isPrivate; - const toggleGroupOptions = [{ value: "mine", label: t("my_availability") }]; - - if (!isOrgAndPrivate || isOrgAdminOrOwner) { - toggleGroupOptions.push({ value: "team", label: t("team_availability") }); - } - - return ( -
- - { - if (!value) return; - router.push(`${pathname}?${createQueryString("type", value)}`); - }} - options={toggleGroupOptions} - /> - -
- }> - {searchParams?.get("type") === "team" && (!isOrgAndPrivate || isOrgAdminOrOwner) ? ( - - ) : ( - - )} - - - ); -} +const AvailabilityPage = () => <_AvailabilityPage />; AvailabilityPage.PageWrapper = PageWrapper; + +export default AvailabilityPage; diff --git a/apps/web/pages/availability/troubleshoot.tsx b/apps/web/pages/availability/troubleshoot.tsx index 9a0817755a..1098bf4951 100644 --- a/apps/web/pages/availability/troubleshoot.tsx +++ b/apps/web/pages/availability/troubleshoot.tsx @@ -1,36 +1,8 @@ -import dynamic from "next/dynamic"; -import { Suspense } from "react"; - -import { getLayout } from "@calcom/features/troubleshooter/layout"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { HeadSeo, Loader } from "@calcom/ui"; - import PageWrapper from "@components/PageWrapper"; -const TroubleshooterClientOnly = dynamic( - () => import("@calcom/features/troubleshooter/Troubleshooter").then((mod) => mod.Troubleshooter), - { - ssr: false, - } -); - -function TroubleshooterPage() { - const { t } = useLocale(); - return ( - <> - - - - - }> - - - - ); -} +import Troubleshoot, { getLayout } from "~/availability/troubleshoot/troubleshoot-view"; +const TroubleshooterPage = () => ; TroubleshooterPage.getLayout = getLayout; TroubleshooterPage.PageWrapper = PageWrapper; export default TroubleshooterPage; diff --git a/apps/web/playwright/fixtures/embeds.ts b/apps/web/playwright/fixtures/embeds.ts index f38f824a72..f480b42879 100644 --- a/apps/web/playwright/fixtures/embeds.ts +++ b/apps/web/playwright/fixtures/embeds.ts @@ -59,7 +59,7 @@ export const createEmbedsFixture = (page: Page) => { api("on", { action: "*", callback: (e) => { - console.log("Playwright Embed Fixture: Received event", e); + console.log("Playwright Embed Fixture: Received event", JSON.stringify(e.detail)); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore window.iframeReady = true; // Technically if there are multiple cal embeds, it can be set due to some other iframe. But it works for now. Improve it when it doesn't work @@ -76,6 +76,22 @@ export const createEmbedsFixture = (page: Page) => { }, { calNamespace } ); + + page.on("console", (msg) => { + console.log(`Browser Console: ${msg.type()}: ${msg.text()}`); + }); + + page.on("framenavigated", async (frame) => { + console.log(`Navigation occurred in frame: ${frame.url()}`); + }); + + page.on("pageerror", (error) => { + console.error(`Page error: ${error.message}`); + }); + + page.on("requestfailed", (request) => { + console.error(`Failed request: ${request.url()}, ${request.failure()?.errorText}`); + }); }, async getActionFiredDetails({ calNamespace, actionType }: { calNamespace: string; actionType: string }) { diff --git a/apps/web/scripts/vercel-production-deploy.sh b/apps/web/scripts/vercel-production-deploy.sh index adb3cf1ad3..d032670fbc 100755 --- a/apps/web/scripts/vercel-production-deploy.sh +++ b/apps/web/scripts/vercel-production-deploy.sh @@ -10,6 +10,7 @@ checkRoute () { # These conditionals are used to remove directories from the build that are not needed in production # This is to reduce the size of the build and prevent OOM errors checkRoute "$APP_ROUTER_EVENT_TYPES_ENABLED" app/future/event-types +checkRoute "$APP_ROUTER_AVAILABILITY_ENABLED" app/future/availability checkRoute "$APP_ROUTER_SETTINGS_ADMIN_ENABLED" app/future/settings/admin checkRoute "$APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED" app/future/apps/installed checkRoute "$APP_ROUTER_APPS_SLUG_ENABLED" app/future/apps/\[slug\] diff --git a/packages/embeds/embed-core/playwright/lib/testUtils.ts b/packages/embeds/embed-core/playwright/lib/testUtils.ts index 01bb74c085..a7d0f96f30 100644 --- a/packages/embeds/embed-core/playwright/lib/testUtils.ts +++ b/packages/embeds/embed-core/playwright/lib/testUtils.ts @@ -38,12 +38,29 @@ export const getEmbedIframe = async ({ page: Page; pathname: string; }) => { + const handleFrameErrors = (frame: Frame) => { + frame.on("pageerror", (error) => { + console.error(`Iframe error (${frame.url()}): ${error.message}`); + }); + frame.on("requestfailed", (request) => { + console.error( + `Iframe failed request (${frame.url()}): ${request.url()}, ${request.failure()?.errorText}` + ); + }); + }; + // We can't seem to access page.frame till contentWindow is available. So wait for that. const iframeReady = await page.evaluate( (hardTimeout) => { return new Promise((resolve) => { const interval = setInterval(() => { const iframe = document.querySelector(".cal-embed"); + if (iframe) { + console.log("Iframe found"); + } + if (iframe?.contentWindow) { + console.log("Iframe contentWindow found"); + } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (iframe && iframe.contentWindow && window.iframeReady) { @@ -74,9 +91,12 @@ export const getEmbedIframe = async ({ return null; } + page.on("frameattached", handleFrameErrors); + // We just verified that iframeReady is true here, so obviously embedIframe is not null // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const embedIframe = page.frame(`cal-embed=${calNamespace}`)!; + handleFrameErrors(embedIframe); const u = new URL(embedIframe.url()); if (u.pathname === `${pathname}/embed`) { return embedIframe; diff --git a/packages/lib/bookingSuccessRedirect.ts b/packages/lib/bookingSuccessRedirect.ts index d3c6b5b1c1..627511845a 100644 --- a/packages/lib/bookingSuccessRedirect.ts +++ b/packages/lib/bookingSuccessRedirect.ts @@ -2,11 +2,12 @@ import type { EventType } from "@prisma/client"; import { useRouter } from "next/navigation"; import type { PaymentPageProps } from "@calcom/ee/payments/pages/payment"; +import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; import type { BookingResponse } from "@calcom/features/bookings/types"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { navigateInTopWindow } from "@calcom/lib/navigateInTopWindow"; -function getNewSeachParams(args: { +function getNewSearchParams(args: { query: Record; searchParams?: URLSearchParams; }) { @@ -44,6 +45,7 @@ export const getBookingRedirectExtraParams = (booking: SuccessRedirectBookingTyp export const useBookingSuccessRedirect = () => { const router = useRouter(); const searchParams = useCompatSearchParams(); + const isEmbed = useIsEmbed(); const bookingSuccessRedirect = ({ successRedirectUrl, query, @@ -63,7 +65,7 @@ export const useBookingSuccessRedirect = () => { return; } const bookingExtraParams = getBookingRedirectExtraParams(booking); - const newSearchParams = getNewSeachParams({ + const newSearchParams = getNewSearchParams({ query: { ...query, ...bookingExtraParams, @@ -78,8 +80,21 @@ export const useBookingSuccessRedirect = () => { navigateInTopWindow(url.toString()); return; } - const newSearchParams = getNewSeachParams({ query }); - return router.push(`/booking/${booking.uid}?${newSearchParams.toString()}`); + + // TODO: Abstract it out and reuse at other places where we navigate within the embed. Though this is needed only in case of hard navigation happening but we aren't sure where hard navigation happens and where a soft navigation + // This is specially true after App Router it seems + const headersRelatedSearchParams = searchParams + ? { + "flag.coep": searchParams.get("flag.coep") ?? "false", + } + : undefined; + + // We don't want to forward all search params, as they could possibly break the booking page. + const newSearchParams = getNewSearchParams({ + query, + searchParams: new URLSearchParams(headersRelatedSearchParams), + }); + return router.push(`/booking/${booking.uid}${isEmbed ? "/embed" : ""}?${newSearchParams.toString()}`); }; return bookingSuccessRedirect; diff --git a/packages/lib/server/repository/schedule.ts b/packages/lib/server/repository/schedule.ts new file mode 100644 index 0000000000..a9e75de317 --- /dev/null +++ b/packages/lib/server/repository/schedule.ts @@ -0,0 +1,20 @@ +import prisma from "@calcom/prisma"; + +export class ScheduleRepository { + static async findScheduleById({ id }: { id: number }) { + const schedule = await prisma.schedule.findUnique({ + where: { + id, + }, + select: { + id: true, + userId: true, + name: true, + availability: true, + timeZone: true, + }, + }); + + return schedule; + } +} diff --git a/packages/platform/atoms/monorepo.ts b/packages/platform/atoms/monorepo.ts index 37b0130a6b..98c9611391 100644 --- a/packages/platform/atoms/monorepo.ts +++ b/packages/platform/atoms/monorepo.ts @@ -1,5 +1,4 @@ export { BookerWebWrapper as Booker } from "./booker"; -export { AvailabilitySettingsWebWrapper as AvailabilitySettings } from "./availability/wrappers/AvailabilitySettingsWebWrapper"; export { CalProvider } from "./cal-provider/CalProvider"; export { useIsPlatform } from "./hooks/useIsPlatform"; export { useAtomsContext } from "./hooks/useAtomsContext"; @@ -9,3 +8,4 @@ export * as Dialog from "./src/components/ui/dialog"; export { Timezone } from "./timezone"; export { SelectedCalendarsSettingsWebWrapper } from "./selected-calendars/wrappers/SelectedCalendarsSettingsWebWrapper"; export { DestinationCalendarSettingsWebWrapper } from "./destination-calendar/wrappers/DestinationCalendarSettingsWebWrapper"; +export * from "./availability"; diff --git a/turbo.json b/turbo.json index d91f6499a2..18ec904aac 100644 --- a/turbo.json +++ b/turbo.json @@ -248,6 +248,7 @@ "APP_ROUTER_AUTH_PLATFORM_ENABLED", "APP_ROUTER_AUTH_OAUTH2_ENABLED", "APP_ROUTER_TEAMS_ENABLED", + "APP_ROUTER_AVAILABILITY_ENABLED", "APP_USER_NAME", "BASECAMP3_CLIENT_ID", "BASECAMP3_CLIENT_SECRET",