chore: App-router-migration(/availability) add pages / extract components into /module (#16404)

* migrate availability/[schedule]

* add use client directive

* migrate main availability page

* migrate availability/troubleshoot page

* add env var

* extract AvailabilitySettingsWebWrapper into /modules

* fix build

* add missing default

* add missing export

* fix troubleshoot

* use params, not searchParams in generateMetadata

* fix schedule page in app router
This commit is contained in:
Benny Joo authored and GitHub committed 2024-09-05 12:37:18 +00:00
1 parent fabcf2055b
commit 963d22648b
18 files changed
+416 -276

No files matched your search

+1
View File
@@ -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
+1
View File
@@ -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,
@@ -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 });
+13
View File
@@ -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 });
@@ -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 });
@@ -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 =
@@ -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<HTMLUListElement>();
return (
<>
{schedules.length === 0 ? (
<div className="flex justify-center">
<EmptyScreen
Icon="clock"
headline={t("new_schedule_heading")}
description={t("new_schedule_description")}
className="w-full"
buttonRaw={<NewScheduleButton />}
/>
</div>
) : (
<>
<div className="border-subtle bg-default overflow-hidden rounded-md border">
<ul className="divide-subtle divide-y" data-testid="schedules" ref={animationParentRef}>
{schedules.map((schedule) => (
<ScheduleListItem
displayOptions={{
hour12: meQuery.data?.timeFormat ? meQuery.data.timeFormat === 12 : undefined,
timeZone: meQuery.data?.timeZone,
weekStart: meQuery.data?.weekStart || "Sunday",
}}
key={schedule.id}
schedule={schedule}
isDeletable={schedules.length !== 1}
updateDefault={updateMutation.mutate}
deleteFunction={deleteMutation.mutate}
duplicateFunction={duplicateMutation.mutate}
/>
))}
</ul>
</div>
<div className="text-default mb-16 mt-4 hidden text-center text-sm md:block">
{t("temporarily_out_of_office")}{" "}
<Link href="settings/my-account/out-of-office" className="underline">
{t("add_a_redirect")}
</Link>
</div>
{bulkUpdateModal && (
<BulkEditDefaultForEventsModal
isPending={bulkUpdateDefaultAvailabilityMutation.isPending}
open={bulkUpdateModal}
setOpen={setBulkUpdateModal}
bulkUpdateFunction={bulkUpdateDefaultAvailabilityMutation.mutate}
description={t("default_schedules_bulk_description")}
/>
)}
</>
)}
</>
);
}
function AvailabilityListWithQuery() {
const query = trpc.viewer.availability.list.useQuery();
return (
<QueryCell
query={query}
success={({ data }) => <AvailabilityList {...data} />}
customLoader={<SkeletonLoader />}
/>
);
}
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 (
<div>
<Shell
heading={t("availability")}
title="Availability"
description="Configure times when you are available for bookings."
hideHeadingOnMobile
withoutMain={false}
subtitle={t("configure_availability")}
CTA={
<div className="flex gap-2">
<ToggleGroup
className="hidden md:block"
defaultValue={searchParams?.get("type") ?? "mine"}
onValueChange={(value) => {
if (!value) return;
router.push(`${pathname}?${createQueryString("type", value)}`);
}}
options={toggleGroupOptions}
/>
<NewScheduleButton />
</div>
}>
{searchParams?.get("type") === "team" && (!isOrgAndPrivate || isOrgAdminOrOwner) ? (
<AvailabilitySliderTable userTimeFormat={me?.data?.timeFormat ?? null} />
) : (
<AvailabilityListWithQuery />
)}
</Shell>
</div>
);
}
@@ -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 (
<>
<HeadSeo title={t("troubleshoot")} description={t("troubleshoot_availability")} />
<Suspense
fallback={
<div className="flex h-full w-full items-center justify-center">
<Loader />
</div>
}>
<TroubleshooterClientOnly month={null} />
</Suspense>
</>
);
}
export { getLayout } from "@calcom/features/troubleshooter/layout";
export default TroubleshooterPage;
+6 -6
View File
@@ -1,9 +1,9 @@
import { AvailabilitySettings } from "@calcom/atoms/monorepo";
import PageWrapper from "@components/PageWrapper";
export default function Availability() {
return <AvailabilitySettings />;
}
import { AvailabilitySettingsWebWrapper } from "~/availability/[schedule]/schedule-view";
Availability.PageWrapper = PageWrapper;
const AvailabilitySettings = () => <AvailabilitySettingsWebWrapper />;
AvailabilitySettings.PageWrapper = PageWrapper;
export default AvailabilitySettings;
+4 -230
View File
@@ -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<HTMLUListElement>();
return (
<>
{schedules.length === 0 ? (
<div className="flex justify-center">
<EmptyScreen
Icon="clock"
headline={t("new_schedule_heading")}
description={t("new_schedule_description")}
className="w-full"
buttonRaw={<NewScheduleButton />}
/>
</div>
) : (
<>
<div className="border-subtle bg-default overflow-hidden rounded-md border">
<ul className="divide-subtle divide-y" data-testid="schedules" ref={animationParentRef}>
{schedules.map((schedule) => (
<ScheduleListItem
displayOptions={{
hour12: meQuery.data?.timeFormat ? meQuery.data.timeFormat === 12 : undefined,
timeZone: meQuery.data?.timeZone,
weekStart: meQuery.data?.weekStart || "Sunday",
}}
key={schedule.id}
schedule={schedule}
isDeletable={schedules.length !== 1}
updateDefault={updateMutation.mutate}
deleteFunction={deleteMutation.mutate}
duplicateFunction={duplicateMutation.mutate}
/>
))}
</ul>
</div>
<div className="text-default mb-16 mt-4 hidden text-center text-sm md:block">
{t("temporarily_out_of_office")}{" "}
<Link href="settings/my-account/out-of-office" className="underline">
{t("add_a_redirect")}
</Link>
</div>
{bulkUpdateModal && (
<BulkEditDefaultForEventsModal
isPending={bulkUpdateDefaultAvailabilityMutation.isPending}
open={bulkUpdateModal}
setOpen={setBulkUpdateModal}
bulkUpdateFunction={bulkUpdateDefaultAvailabilityMutation.mutate}
description={t("default_schedules_bulk_description")}
/>
)}
</>
)}
</>
);
}
function AvailabilityListWithQuery() {
const query = trpc.viewer.availability.list.useQuery();
return (
<QueryCell
query={query}
success={({ data }) => <AvailabilityList {...data} />}
customLoader={<SkeletonLoader />}
/>
);
}
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 (
<div>
<Shell
heading={t("availability")}
title="Availability"
description="Configure times when you are available for bookings."
hideHeadingOnMobile
withoutMain={false}
subtitle={t("configure_availability")}
CTA={
<div className="flex gap-2">
<ToggleGroup
className="hidden md:block"
defaultValue={searchParams?.get("type") ?? "mine"}
onValueChange={(value) => {
if (!value) return;
router.push(`${pathname}?${createQueryString("type", value)}`);
}}
options={toggleGroupOptions}
/>
<NewScheduleButton />
</div>
}>
{searchParams?.get("type") === "team" && (!isOrgAndPrivate || isOrgAdminOrOwner) ? (
<AvailabilitySliderTable userTimeFormat={me?.data?.timeFormat ?? null} />
) : (
<AvailabilityListWithQuery />
)}
</Shell>
</div>
);
}
const AvailabilityPage = () => <_AvailabilityPage />;
AvailabilityPage.PageWrapper = PageWrapper;
export default AvailabilityPage;
+2 -30
View File
@@ -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 (
<>
<HeadSeo title={t("troubleshoot")} description={t("troubleshoot_availability")} />
<Suspense
fallback={
<div className="flex h-full w-full items-center justify-center">
<Loader />
</div>
}>
<TroubleshooterClientOnly month={null} />
</Suspense>
</>
);
}
import Troubleshoot, { getLayout } from "~/availability/troubleshoot/troubleshoot-view";
const TroubleshooterPage = () => <Troubleshoot />;
TroubleshooterPage.getLayout = getLayout;
TroubleshooterPage.PageWrapper = PageWrapper;
export default TroubleshooterPage;
+17 -1
View File
@@ -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 }) {
@@ -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\]
@@ -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<HTMLIFrameElement>(".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;
+19 -4
View File
@@ -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<string, string | null | undefined | boolean>;
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;
@@ -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;
}
}
+1 -1
View File
@@ -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";
+1
View File
@@ -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",