Skip to content

Commit 14563f6

Browse files
feat: re-render bookings page after feature opt-in without full refresh (calcom#27873)
* feat: re-render bookings page after feature opt-in without full refresh Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * refactor: make onOptInSuccess a required prop Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent c7e66a3 commit 14563f6

3 files changed

Lines changed: 36 additions & 7 deletions

File tree

apps/web/modules/bookings/views/bookings-view.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import FeatureOptInBannerWrapper from "~/feature-opt-in/components/FeatureOptInB
66
import { useLocale } from "@calcom/lib/hooks/useLocale";
77
import classNames from "@calcom/ui/classNames";
88
import dynamic from "next/dynamic";
9-
import { usePathname } from "next/navigation";
10-
import { useMemo } from "react";
9+
import { usePathname, useRouter } from "next/navigation";
10+
import { useCallback, useMemo } from "react";
1111
import { useFeatureOptInBanner } from "../../feature-opt-in/hooks/useFeatureOptInBanner";
1212
import { BookingListContainer } from "../components/BookingListContainer";
1313
import { useActiveFiltersValidator } from "../hooks/useActiveFiltersValidator";
@@ -78,7 +78,11 @@ export default function Bookings(props: BookingsProps) {
7878

7979
function BookingsContent({ status, permissions, bookingsV3Enabled, bookingAuditEnabled }: BookingsProps) {
8080
const [view] = useBookingsView({ bookingsV3Enabled });
81-
const optInBanner = useFeatureOptInBanner("bookings-v3");
81+
const router = useRouter();
82+
const handleOptInSuccess = useCallback(() => {
83+
router.refresh();
84+
}, [router]);
85+
const optInBanner = useFeatureOptInBanner("bookings-v3", { onOptInSuccess: handleOptInSuccess });
8286

8387
return (
8488
<div className={classNames(view === "calendar" && "-mb-8")}>

apps/web/modules/feature-opt-in/hooks/useFeatureOptInBanner.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ describe("useFeatureOptInBanner", () => {
7575
it("shows banner for normal users when eligible", () => {
7676
mockUseSession.mockReturnValue({ data: { user: {} } });
7777

78-
const { result } = renderHook(() => useFeatureOptInBanner("bookings-v3"));
78+
const { result } = renderHook(() =>
79+
useFeatureOptInBanner("bookings-v3", { onOptInSuccess: vi.fn() })
80+
);
7981

8082
expect(result.current.shouldShow).toBe(true);
8183
});
@@ -85,8 +87,23 @@ describe("useFeatureOptInBanner", () => {
8587
data: { user: { impersonatedBy: { id: 999, email: "admin@cal.com" } } },
8688
});
8789

88-
const { result } = renderHook(() => useFeatureOptInBanner("bookings-v3"));
90+
const { result } = renderHook(() =>
91+
useFeatureOptInBanner("bookings-v3", { onOptInSuccess: vi.fn() })
92+
);
8993

9094
expect(result.current.shouldShow).toBe(true);
9195
});
96+
97+
it("calls onOptInSuccess callback when markOptedIn is invoked", () => {
98+
mockUseSession.mockReturnValue({ data: { user: {} } });
99+
const onOptInSuccess = vi.fn();
100+
101+
const { result } = renderHook(() =>
102+
useFeatureOptInBanner("bookings-v3", { onOptInSuccess })
103+
);
104+
105+
result.current.markOptedIn();
106+
107+
expect(onOptInSuccess).toHaveBeenCalledOnce();
108+
});
92109
});

apps/web/modules/feature-opt-in/hooks/useFeatureOptInBanner.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,14 @@ function isFeatureOptedIn(featureId: string): boolean {
5858
return getFeatureOptInTimestamp(featureId) !== null;
5959
}
6060

61-
function useFeatureOptInBanner(featureId: string): UseFeatureOptInBannerResult {
61+
type UseFeatureOptInBannerOptions = {
62+
onOptInSuccess: () => void;
63+
};
64+
65+
function useFeatureOptInBanner(
66+
featureId: string,
67+
options: UseFeatureOptInBannerOptions
68+
): UseFeatureOptInBannerResult {
6269
const [isDialogOpen, setIsDialogOpen] = useState(false);
6370
const [isDismissed, setIsDismissed] = useState(() => isFeatureDismissed(featureId));
6471
const [isOptedIn, setIsOptedIn] = useState(() => isFeatureOptedIn(featureId));
@@ -135,7 +142,8 @@ function useFeatureOptInBanner(featureId: string): UseFeatureOptInBannerResult {
135142
const markOptedIn = useCallback(() => {
136143
setFeatureOptedIn(featureId);
137144
setIsOptedIn(true);
138-
}, [featureId]);
145+
options.onOptInSuccess();
146+
}, [featureId, options.onOptInSuccess]);
139147

140148
const openDialog = useCallback(() => {
141149
posthog.capture("feature_opt_in_banner_try_it_clicked", {

0 commit comments

Comments
 (0)