feat: re-render bookings page after feature opt-in without full refresh (#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>
This commit is contained in:
Eunjae Lee
2026-02-12 10:41:40 +05:30
committed by GitHub
co-authored by Devin AI
parent c7e66a3aa9
commit 14563f664e
3 changed files with 36 additions and 7 deletions
@@ -6,8 +6,8 @@ import FeatureOptInBannerWrapper from "~/feature-opt-in/components/FeatureOptInB
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import dynamic from "next/dynamic";
import { usePathname } from "next/navigation";
import { useMemo } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useCallback, useMemo } from "react";
import { useFeatureOptInBanner } from "../../feature-opt-in/hooks/useFeatureOptInBanner";
import { BookingListContainer } from "../components/BookingListContainer";
import { useActiveFiltersValidator } from "../hooks/useActiveFiltersValidator";
@@ -78,7 +78,11 @@ export default function Bookings(props: BookingsProps) {
function BookingsContent({ status, permissions, bookingsV3Enabled, bookingAuditEnabled }: BookingsProps) {
const [view] = useBookingsView({ bookingsV3Enabled });
const optInBanner = useFeatureOptInBanner("bookings-v3");
const router = useRouter();
const handleOptInSuccess = useCallback(() => {
router.refresh();
}, [router]);
const optInBanner = useFeatureOptInBanner("bookings-v3", { onOptInSuccess: handleOptInSuccess });
return (
<div className={classNames(view === "calendar" && "-mb-8")}>
@@ -75,7 +75,9 @@ describe("useFeatureOptInBanner", () => {
it("shows banner for normal users when eligible", () => {
mockUseSession.mockReturnValue({ data: { user: {} } });
const { result } = renderHook(() => useFeatureOptInBanner("bookings-v3"));
const { result } = renderHook(() =>
useFeatureOptInBanner("bookings-v3", { onOptInSuccess: vi.fn() })
);
expect(result.current.shouldShow).toBe(true);
});
@@ -85,8 +87,23 @@ describe("useFeatureOptInBanner", () => {
data: { user: { impersonatedBy: { id: 999, email: "admin@cal.com" } } },
});
const { result } = renderHook(() => useFeatureOptInBanner("bookings-v3"));
const { result } = renderHook(() =>
useFeatureOptInBanner("bookings-v3", { onOptInSuccess: vi.fn() })
);
expect(result.current.shouldShow).toBe(true);
});
it("calls onOptInSuccess callback when markOptedIn is invoked", () => {
mockUseSession.mockReturnValue({ data: { user: {} } });
const onOptInSuccess = vi.fn();
const { result } = renderHook(() =>
useFeatureOptInBanner("bookings-v3", { onOptInSuccess })
);
result.current.markOptedIn();
expect(onOptInSuccess).toHaveBeenCalledOnce();
});
});
@@ -58,7 +58,14 @@ function isFeatureOptedIn(featureId: string): boolean {
return getFeatureOptInTimestamp(featureId) !== null;
}
function useFeatureOptInBanner(featureId: string): UseFeatureOptInBannerResult {
type UseFeatureOptInBannerOptions = {
onOptInSuccess: () => void;
};
function useFeatureOptInBanner(
featureId: string,
options: UseFeatureOptInBannerOptions
): UseFeatureOptInBannerResult {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isDismissed, setIsDismissed] = useState(() => isFeatureDismissed(featureId));
const [isOptedIn, setIsOptedIn] = useState(() => isFeatureOptedIn(featureId));
@@ -135,7 +142,8 @@ function useFeatureOptInBanner(featureId: string): UseFeatureOptInBannerResult {
const markOptedIn = useCallback(() => {
setFeatureOptedIn(featureId);
setIsOptedIn(true);
}, [featureId]);
options.onOptInSuccess();
}, [featureId, options.onOptInSuccess]);
const openDialog = useCallback(() => {
posthog.capture("feature_opt_in_banner_try_it_clicked", {