Skip to content

Commit dd7f108

Browse files
authored
fix: put booking details and calendar behind feature flag (calcom#25175)
* Revert "fix: revert bookings redesign (calcom#25172)" This reverts commit 1f102bf. * add bookings-v3 feature flag * put things behind a feature flag * remove no longer needed test * revert e2e tests * put back description * revert AvatarGroup * apply feedback * remove "view" booking action * remove Alert (When the bookings query errors, this branch now renders only the alert and skips the data-table filter/segment controls. Those controls moved into BookingsList, so in error states users can no longer clear or tweak filters to recover from the failure, effectively trapping them behind the alert.) * address feedback * revert useMediaQuery
1 parent 7e4d9e2 commit dd7f108

32 files changed

Lines changed: 1548 additions & 690 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client";
2+
3+
import { useQueryState } from "nuqs";
4+
import { useEffect } from "react";
5+
6+
import { activeFiltersParser } from "@calcom/features/data-table/lib/parsers";
7+
import { useLocale } from "@calcom/lib/hooks/useLocale";
8+
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
9+
import { ToggleGroup } from "@calcom/ui/components/form";
10+
import { Icon } from "@calcom/ui/components/icon";
11+
12+
import { viewParser, type BookingView } from "~/bookings/lib/viewParser";
13+
14+
export function ViewToggleButton() {
15+
const { t } = useLocale();
16+
const [view, setView] = useQueryState(
17+
"view",
18+
viewParser.withDefault("list").withOptions({ clearOnDefault: true })
19+
);
20+
const [, setActiveFilters] = useQueryState("activeFilters", activeFiltersParser);
21+
const isMobile = !useMediaQuery("(min-width: 640px)");
22+
23+
useEffect(() => {
24+
// Force list view on mobile
25+
if (isMobile && view === "calendar") {
26+
// Clear dateRange filter when forcing calendar to list view, same as manual toggle
27+
setActiveFilters((prev) => prev?.filter((filter) => filter.f !== "dateRange") ?? []);
28+
setView("list");
29+
}
30+
}, [isMobile, view, setView, setActiveFilters]);
31+
32+
return (
33+
<div className="hidden sm:block">
34+
<ToggleGroup
35+
value={view}
36+
onValueChange={(value) => {
37+
if (!value) return;
38+
const newView = value as BookingView;
39+
40+
// When switching from calendar to list view, remove the dateRange filter
41+
if (view === "calendar" && newView === "list") {
42+
setActiveFilters((prev) => prev?.filter((filter) => filter.f !== "dateRange") ?? []);
43+
}
44+
45+
setView(newView);
46+
}}
47+
options={[
48+
{
49+
value: "list",
50+
label: "",
51+
tooltip: t("list_view"),
52+
iconLeft: <Icon name="menu" className="h-4 w-4" />,
53+
},
54+
{
55+
value: "calendar",
56+
label: "",
57+
tooltip: t("calendar_view"),
58+
iconLeft: <Icon name="calendar" className="h-4 w-4" />,
59+
},
60+
]}
61+
/>
62+
</div>
63+
);
64+
}

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { buildLegacyRequest } from "@lib/buildLegacyCtx";
1616
import { validStatuses } from "~/bookings/lib/validStatuses";
1717
import BookingsList from "~/bookings/views/bookings-view";
1818

19+
import { ViewToggleButton } from "./ViewToggleButton";
20+
1921
const querySchema = z.object({
2022
status: z.enum(validStatuses),
2123
});
@@ -54,18 +56,23 @@ const Page = async ({ params }: PageProps) => {
5456
canReadOthersBookings = teamIdsWithPermission.length > 0;
5557
}
5658

59+
const userProfile = session?.user?.profile;
60+
const orgId = userProfile?.organizationId ?? session?.user.org?.id;
5761
const featuresRepository = new FeaturesRepository(prisma);
58-
const isCalendarViewEnabled = await featuresRepository.checkIfFeatureIsEnabledGlobally(
59-
"booking-calendar-view"
60-
);
62+
const bookingsV3Enabled = orgId
63+
? await featuresRepository.checkIfTeamHasFeature(orgId, "bookings-v3")
64+
: false;
6165

6266
return (
63-
<ShellMainAppDir heading={t("bookings")} subtitle={t("bookings_description")}>
67+
<ShellMainAppDir
68+
heading={t("bookings")}
69+
subtitle={t("bookings_description")}
70+
CTA={bookingsV3Enabled ? <ViewToggleButton /> : null}>
6471
<BookingsList
6572
status={parsed.data.status}
6673
userId={session?.user?.id}
6774
permissions={{ canReadOthersBookings }}
68-
isCalendarViewEnabled={isCalendarViewEnabled}
75+
bookingsV3Enabled={bookingsV3Enabled}
6976
/>
7077
</ShellMainAppDir>
7178
);

apps/web/components/booking/BookingListItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ function BookingListItem(booking: BookingItemProps) {
499499
</div>
500500
<div className="flex w-full flex-col flex-wrap items-end justify-end space-x-2 space-y-2 py-4 pl-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4 sm:flex-row sm:flex-nowrap sm:items-start sm:space-y-0 sm:pl-0">
501501
{shouldShowPendingActions(actionContext) && <TableActions actions={pendingActions} />}
502-
<BookingActionsDropdown booking={booking} context="booking-list-item" />
502+
<BookingActionsDropdown booking={booking} context="list" />
503503
{shouldShowRecurringCancelAction(actionContext) && <TableActions actions={[cancelEventAction]} />}
504504
{shouldShowIndividualReportButton(actionContext) && (
505505
<div className="flex items-center space-x-2">

apps/web/components/booking/SkeletonLoader.tsx

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,20 @@ import { SkeletonText } from "@calcom/ui/components/skeleton";
55
function SkeletonLoader() {
66
return (
77
<ul className="divide-subtle border-subtle bg-default animate-pulse divide-y rounded-md border sm:overflow-hidden">
8+
{/* TODAY header */}
9+
<li className="bg-muted flex items-center px-6 py-4">
10+
<SkeletonText className="h-4 w-16" />
11+
</li>
812
<SkeletonItem />
913
<SkeletonItem />
1014
<SkeletonItem />
15+
16+
{/* NEXT header */}
17+
<li className="bg-muted px-4 py-2">
18+
<SkeletonText className="h-4 w-16" />
19+
</li>
20+
<SkeletonItem />
21+
<SkeletonItem />
1122
</ul>
1223
);
1324
}
@@ -16,21 +27,42 @@ export default SkeletonLoader;
1627

1728
function SkeletonItem() {
1829
return (
19-
<li className="group flex w-full items-center justify-between px-4 py-4 sm:px-6">
20-
<div className="flex-grow truncate text-sm">
21-
<div className="flex">
22-
<div className="flex flex-col space-y-2">
23-
<SkeletonText className="h-5 w-16" />
24-
<SkeletonText className="h-4 w-32" />
25-
</div>
30+
<li className="group flex w-full items-start justify-between px-4 py-4 sm:px-6">
31+
{/* Left side - Date and time info */}
32+
<div className="flex min-w-0 flex-shrink-0 flex-col gap-2 pr-4">
33+
<SkeletonText className="h-4 w-24" />
34+
<SkeletonText className="h-4 w-28" />
35+
<div className="mt-1 flex items-center gap-2">
36+
<SkeletonText className="h-4 w-4 rounded" />
37+
<SkeletonText className="h-3 w-20" />
2638
</div>
27-
</div>
28-
<div className="mt-4 hidden flex-shrink-0 sm:ml-5 sm:mt-0 lg:flex">
29-
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
30-
<SkeletonText className="h-6 w-16" />
31-
<SkeletonText className="h-6 w-32" />
39+
{/* Badges */}
40+
<div className="mt-3 flex gap-2">
41+
<SkeletonText className="h-5 w-14 rounded-md" />
3242
</div>
3343
</div>
44+
45+
{/* Right side - Event details */}
46+
<div className="flex min-w-0 flex-1 flex-col space-y-2">
47+
{/* Event title */}
48+
<SkeletonText className="h-5 w-36" />
49+
{/* Event description */}
50+
<SkeletonText className="h-4 w-48" />
51+
{/* Attendees */}
52+
<SkeletonText className="h-4 w-56" />
53+
</div>
54+
55+
{/* Action buttons - only visible on larger screens */}
56+
<div className="ml-4 hidden flex-shrink-0 gap-2 sm:flex">
57+
<SkeletonText className="h-9 w-20 rounded-md" />
58+
<SkeletonText className="h-9 w-20 rounded-md" />
59+
<SkeletonText className="h-9 w-9 rounded-md" />
60+
</div>
61+
62+
{/* Mobile menu button */}
63+
<div className="ml-4 flex flex-shrink-0 sm:hidden">
64+
<SkeletonText className="h-9 w-9 rounded-md" />
65+
</div>
3466
</li>
3567
);
3668
}

0 commit comments

Comments
 (0)