Skip to content

Commit ee31633

Browse files
hbjORbjPeerRich
andauthored
perf: layout persistence and reduced bundle size (calcom#18889)
* create /(main-nav)/layout.tsx file that uses Shell * Add ShellMainAppDir that clones ShellMain but with client/server separation * event-types, availability, bookings * update pagesAndRewritePaths logic * fix --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com>
1 parent 8be2ea8 commit ee31633

File tree

10 files changed

+213
-102
lines changed

10 files changed

+213
-102
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { ShellMainAppDirBackButton } from "app/(use-page-wrapper)/(main-nav)/ShellMainAppDirBackButton";
2+
import classNames from "classnames";
3+
4+
import type { LayoutProps } from "@calcom/features/shell/Shell";
5+
6+
// Copied from `ShellMain` but with a different `ShellMainAppDirBackButton` import
7+
// for client/server component separation
8+
export function ShellMainAppDir(props: LayoutProps) {
9+
return (
10+
<>
11+
{(props.heading || !!props.backPath) && (
12+
<div
13+
className={classNames(
14+
"flex items-center md:mb-6 md:mt-0",
15+
props.smallHeading ? "lg:mb-7" : "lg:mb-8",
16+
props.hideHeadingOnMobile ? "mb-0" : "mb-6"
17+
)}>
18+
{!!props.backPath && <ShellMainAppDirBackButton backPath={props.backPath} />}
19+
{props.heading && (
20+
<header
21+
className={classNames(props.large && "py-8", "flex w-full max-w-full items-center truncate")}>
22+
{props.HeadingLeftIcon && <div className="ltr:mr-4">{props.HeadingLeftIcon}</div>}
23+
<div
24+
className={classNames("w-full truncate ltr:mr-4 rtl:ml-4 md:block", props.headerClassName)}>
25+
{props.heading && (
26+
<h3
27+
className={classNames(
28+
"font-cal text-emphasis max-w-28 sm:max-w-72 md:max-w-80 inline truncate text-lg font-semibold tracking-wide sm:text-xl md:block xl:max-w-full",
29+
props.smallHeading ? "text-base" : "text-xl",
30+
props.hideHeadingOnMobile && "hidden"
31+
)}>
32+
{props.heading}
33+
</h3>
34+
)}
35+
{props.subtitle && (
36+
<p className="text-default hidden text-sm md:block" data-testid="subtitle">
37+
{props.subtitle}
38+
</p>
39+
)}
40+
</div>
41+
{props.beforeCTAactions}
42+
{props.CTA && (
43+
<div
44+
className={classNames(
45+
props.backPath
46+
? "relative"
47+
: "pwa:bottom-[max(7rem,_calc(5rem_+_env(safe-area-inset-bottom)))] fixed bottom-20 z-40 ltr:right-4 rtl:left-4 md:z-auto md:ltr:right-0 md:rtl:left-0",
48+
"flex-shrink-0 [-webkit-app-region:no-drag] md:relative md:bottom-auto md:right-auto"
49+
)}>
50+
{props.CTA}
51+
</div>
52+
)}
53+
{props.actions && props.actions}
54+
</header>
55+
)}
56+
</div>
57+
)}
58+
{props.afterHeading && <>{props.afterHeading}</>}
59+
60+
<div className={classNames(props.flexChildrenContainer && "flex flex-1 flex-col")}>
61+
{props.children}
62+
</div>
63+
</>
64+
);
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
5+
import type { LayoutProps } from "@calcom/features/shell/Shell";
6+
import { Button } from "@calcom/ui";
7+
8+
export const ShellMainAppDirBackButton = ({ backPath }: { backPath: LayoutProps["backPath"] }) => {
9+
const router = useRouter();
10+
return (
11+
<Button
12+
variant="icon"
13+
size="sm"
14+
color="minimal"
15+
onClick={() => (typeof backPath === "string" ? router.push(backPath as string) : router.back())}
16+
StartIcon="arrow-left"
17+
aria-label="Go Back"
18+
className="rounded-md ltr:mr-2 rtl:ml-2"
19+
data-testid="go-back-button"
20+
/>
21+
);
22+
};

apps/web/app/(use-page-wrapper)/availability/page.tsx apps/web/app/(use-page-wrapper)/(main-nav)/availability/page.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { _generateMetadata } from "app/_utils";
1+
import { _generateMetadata, getTranslate } from "app/_utils";
22
// import { cookies, headers } from "next/headers";
33
import { notFound } from "next/navigation";
44

55
// import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
66
// import { buildLegacyRequest } from "@lib/buildLegacyCtx";
77
// import { OrganizationRepository } from "@calcom/lib/server/repository/organization";
8-
import AvailabilityPage from "~/availability/availability-view";
8+
import AvailabilityPage, { AvailabilityCTA } from "~/availability/availability-view";
9+
10+
import { ShellMainAppDir } from "../ShellMainAppDir";
911

1012
export const generateMetadata = async () => {
1113
return await _generateMetadata(
@@ -27,10 +29,14 @@ const Page = async () => {
2729
// orgId,
2830
// userId,
2931
// });
32+
const t = await getTranslate();
3033
return (
31-
<AvailabilityPage
32-
// currentOrg={currentOrg}
33-
/>
34+
<ShellMainAppDir
35+
heading={t("availability")}
36+
subtitle={t("configure_availability")}
37+
CTA={<AvailabilityCTA />}>
38+
<AvailabilityPage />
39+
</ShellMainAppDir>
3440
);
3541
} catch {
3642
notFound();

apps/web/app/(use-page-wrapper)/bookings/[status]/page.tsx apps/web/app/(use-page-wrapper)/(main-nav)/bookings/[status]/page.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { ShellMainAppDir } from "app/(use-page-wrapper)/(main-nav)/ShellMainAppDir";
12
import type { PageProps } from "app/_types";
2-
import { _generateMetadata } from "app/_utils";
3+
import { _generateMetadata, getTranslate } from "app/_utils";
34
import { redirect } from "next/navigation";
45
import { z } from "zod";
56

@@ -16,13 +17,18 @@ export const generateMetadata = async () =>
1617
(t) => t("bookings_description")
1718
);
1819

19-
const Page = ({ params }: PageProps) => {
20+
const Page = async ({ params }: PageProps) => {
2021
const parsed = querySchema.safeParse(params);
2122
if (!parsed.success) {
2223
redirect("/bookings/upcoming");
2324
}
25+
const t = await getTranslate();
2426

25-
return <BookingsList status={parsed.data.status} />;
27+
return (
28+
<ShellMainAppDir heading={t("bookings")} subtitle={t("bookings_description")}>
29+
<BookingsList status={parsed.data.status} />
30+
</ShellMainAppDir>
31+
);
2632
};
2733

2834
export default Page;

apps/web/app/(use-page-wrapper)/event-types/page.tsx apps/web/app/(use-page-wrapper)/(main-nav)/event-types/page.tsx

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { ShellMainAppDir } from "app/(use-page-wrapper)/(main-nav)/ShellMainAppDir";
12
import type { PageProps } from "app/_types";
2-
import { _generateMetadata } from "app/_utils";
3+
import { _generateMetadata, getTranslate } from "app/_utils";
34
import { cookies, headers } from "next/headers";
45
import { redirect } from "next/navigation";
56

@@ -9,7 +10,7 @@ import { buildLegacyCtx } from "@lib/buildLegacyCtx";
910

1011
import { ssrInit } from "@server/lib/ssr";
1112

12-
import EventTypes from "~/event-types/views/event-types-listing-view";
13+
import EventTypes, { EventTypesCTA } from "~/event-types/views/event-types-listing-view";
1314

1415
export const generateMetadata = async () =>
1516
await _generateMetadata(
@@ -20,13 +21,22 @@ export const generateMetadata = async () =>
2021
const Page = async ({ params, searchParams }: PageProps) => {
2122
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
2223
const session = await getServerSession({ req: context.req });
24+
2325
if (!session?.user?.id) {
2426
redirect("/auth/login");
2527
}
2628

2729
await ssrInit(context);
28-
29-
return <EventTypes />;
30+
const t = await getTranslate();
31+
32+
return (
33+
<ShellMainAppDir
34+
heading={t("event_types_page_title")}
35+
subtitle={t("event_types_page_subtitle")}
36+
CTA={<EventTypesCTA />}>
37+
<EventTypes />
38+
</ShellMainAppDir>
39+
);
3040
};
3141

3242
export default Page;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Shell from "@calcom/features/shell/Shell";
2+
3+
const Layout = ({ children }: { children: React.ReactNode }) => {
4+
return (
5+
<Shell hideHeadingOnMobile withoutSeo={true} withoutMain={true}>
6+
{children}
7+
</Shell>
8+
);
9+
};
10+
11+
export default Layout;

apps/web/app/(use-page-wrapper)/bookings/layout.tsx

-21
This file was deleted.

apps/web/modules/availability/availability-view.tsx

+40-35
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import SkeletonLoader from "@calcom/features/availability/components/SkeletonLoa
99
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
1010
import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
1111
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
12-
import Shell from "@calcom/features/shell/Shell";
1312
import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable";
1413
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
1514
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -195,14 +194,11 @@ type PageProps = {
195194
currentOrg?: Awaited<ReturnType<typeof OrganizationRepository.findCurrentOrg>> | null;
196195
};
197196

198-
export default function AvailabilityPage({ currentOrg }: PageProps) {
197+
export const AvailabilityCTA = () => {
199198
const { t } = useLocale();
200199
const searchParams = useCompatSearchParams();
201200
const router = useRouter();
202201
const pathname = usePathname();
203-
const me = useMeQuery();
204-
const { data: _data } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { enabled: !currentOrg });
205-
const data = currentOrg ?? _data;
206202

207203
// Get a new searchParams string by merging the current
208204
// searchParams with a provided key/value pair
@@ -216,7 +212,7 @@ export default function AvailabilityPage({ currentOrg }: PageProps) {
216212
[searchParams]
217213
);
218214

219-
const isOrg = Boolean(data);
215+
const { data } = trpc.viewer.organizations.listCurrent.useQuery();
220216
const isOrgAdminOrOwner =
221217
(data && (data.user.role === MembershipRole.OWNER || data.user.role === MembershipRole.ADMIN)) ?? false;
222218
const isOrgAndPrivate = data?.isOrganization && data.isPrivate;
@@ -230,35 +226,44 @@ export default function AvailabilityPage({ currentOrg }: PageProps) {
230226
}
231227

232228
return (
233-
<div>
234-
<Shell
235-
heading={t("availability")}
236-
subtitle={t("configure_availability")}
237-
title={t("availability")}
238-
description={t("configure_availability")}
239-
hideHeadingOnMobile
240-
withoutSeo={true}
241-
withoutMain={false}
242-
CTA={
243-
<div className="flex gap-2">
244-
<ToggleGroup
245-
className="hidden md:block"
246-
defaultValue={searchParams?.get("type") ?? "mine"}
247-
onValueChange={(value) => {
248-
if (!value) return;
249-
router.push(`${pathname}?${createQueryString("type", value)}`);
250-
}}
251-
options={toggleGroupOptions}
252-
/>
253-
<NewScheduleButton />
254-
</div>
255-
}>
256-
{searchParams?.get("type") === "team" && canViewTeamAvailability ? (
257-
<AvailabilitySliderTable userTimeFormat={me?.data?.timeFormat ?? null} isOrg={isOrg} />
258-
) : (
259-
<AvailabilityListWithQuery />
260-
)}
261-
</Shell>
229+
<div className="flex gap-2">
230+
<ToggleGroup
231+
className="hidden md:block"
232+
defaultValue={searchParams?.get("type") ?? "mine"}
233+
onValueChange={(value) => {
234+
if (!value) return;
235+
router.push(`${pathname}?${createQueryString("type", value)}`);
236+
}}
237+
options={toggleGroupOptions}
238+
/>
239+
<NewScheduleButton />
262240
</div>
263241
);
242+
};
243+
244+
export default function AvailabilityPage({ currentOrg }: PageProps) {
245+
const { t } = useLocale();
246+
const searchParams = useCompatSearchParams();
247+
const me = useMeQuery();
248+
const { data: _data } = trpc.viewer.organizations.listCurrent.useQuery(undefined, { enabled: !currentOrg });
249+
const data = currentOrg ?? _data;
250+
251+
const isOrg = Boolean(data);
252+
const isOrgAdminOrOwner =
253+
(data && (data.user.role === MembershipRole.OWNER || data.user.role === MembershipRole.ADMIN)) ?? false;
254+
const isOrgAndPrivate = data?.isOrganization && data.isPrivate;
255+
256+
const canViewTeamAvailability = isOrgAdminOrOwner || !isOrgAndPrivate;
257+
258+
const toggleGroupOptions = [{ value: "mine", label: t("my_availability") }];
259+
260+
if (canViewTeamAvailability) {
261+
toggleGroupOptions.push({ value: "team", label: t("team_availability") });
262+
}
263+
264+
return searchParams?.get("type") === "team" && canViewTeamAvailability ? (
265+
<AvailabilitySliderTable userTimeFormat={me?.data?.timeFormat ?? null} isOrg={isOrg} />
266+
) : (
267+
<AvailabilityListWithQuery />
268+
);
264269
}

0 commit comments

Comments
 (0)