Files
calendar/apps/web/modules/bookings/hooks/useSwitchToCorrectStatusTab.ts
T
Hariom BalharaGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Udit Takkar
2bc17313fd fix: deep link reschedule audit log to booking drawer history tab (#27709)
* fix: deep link reschedule audit log to booking drawer history tab

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: make booking drawer tab-agnostic for cross-tab deep links

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: use status-agnostic /bookings URL for audit log deep links

- Update audit service URLs from /bookings/upcoming?uid=... to /bookings?uid=...
- Add /bookings/page.tsx redirect that routes to /bookings/upcoming preserving query params
- Update tests to expect new URL format

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fixes

* refactor: use client-side replaceState instead of server redirect for booking deep links

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: use router.replace instead of replaceState to update tab and booking list

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* improvements

* refactor: extract deep link logic from BookingListContainer into usePreSelectedBooking hook

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: use proper BookingOutput status type in test helper

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: decouple getTabForBooking from BookingOutput type for simpler testing

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: accept Date | string for endTime in BookingForTabResolution interface

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor: eliminate initialBookingUid prop drilling and revert formatting-only changes

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: use AuditDeepLink wrapper to preserve target=_blank through ServerTrans cloneElement

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* Improve code organization

* refactor: rename usePreSelectedBooking to useSwitchToCorrectStatusTab

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: push preSelectedBooking into store so drawer opens on direct navigation

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fixes

* fixes

* fixes

* fix

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2026-02-17 13:20:06 +05:30

104 lines
3.3 KiB
TypeScript

import { trpc } from "@calcom/trpc/react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState, useTransition } from "react";
import { validStatuses } from "../lib/validStatuses";
import type { BookingListingStatus, BookingOutput } from "../types";
interface BookingForTabResolution {
status: string;
endTime: Date | string;
recurringEventId: string | null;
}
interface UseSwitchToCorrectTabResult {
resolvedTabStatus: BookingListingStatus;
isResolvingTabStatus: boolean;
preSelectedBooking: BookingOutput | null;
}
export function getTabForBooking(booking: BookingForTabResolution): BookingListingStatus {
const isPast = new Date(booking.endTime) <= new Date();
if (booking.status === "CANCELLED" || booking.status === "REJECTED") {
return "cancelled";
}
if (booking.status === "PENDING" && !isPast) {
return "unconfirmed";
}
if (isPast) {
return "past";
}
if (booking.recurringEventId) {
return "recurring";
}
return "upcoming";
}
/**
* Ensures that the correct status tab is selected based on the pre-selected booking through query params
*/
export function useSwitchToCorrectStatusTab({
defaultStatus,
}: {
defaultStatus: BookingListingStatus;
}): UseSwitchToCorrectTabResult {
const {
preSelectedBookingUid,
preSelectedBooking,
preSelectedBookingFull,
isPending: isFetchingPreSelectedBooking,
} = usePreSelectedBooking();
const pathname = usePathname();
const router = useRouter();
const [resolvedTabStatus, setResolvedTab] = useState<BookingListingStatus>(defaultStatus);
const [isNavigatingToCorrectTab, startNavigationToCorrectTab] = useTransition();
useEffect(() => {
if (!preSelectedBooking) return;
const correctTab = getTabForBooking(preSelectedBooking);
const currentTab = pathname?.match(/\/bookings\/(\w+)/)?.[1];
const shouldNavigate = correctTab && currentTab && correctTab !== currentTab;
if (!shouldNavigate) return;
startNavigationToCorrectTab(() => {
const newPath = pathname.replace(`/bookings/${currentTab}`, `/bookings/${correctTab}`);
router.replace(`${newPath}${window.location.search}`);
});
setResolvedTab(correctTab);
}, [preSelectedBooking, pathname, router]);
const isResolvingTabStatus = preSelectedBookingUid
? isFetchingPreSelectedBooking || isNavigatingToCorrectTab
: false;
return { resolvedTabStatus, isResolvingTabStatus, preSelectedBooking: preSelectedBookingFull };
}
export function usePreSelectedBooking(): {
preSelectedBookingUid: string | undefined;
preSelectedBooking: BookingForTabResolution | null;
preSelectedBookingFull: BookingOutput | null;
isPending: boolean;
}{
const searchParams = useSearchParams();
const preSelectedBookingUid = searchParams?.get("uid") ?? undefined;
const { data: preSelectedBookingData, isPending } = trpc.viewer.bookings.get.useQuery(
{
limit: 1,
offset: 0,
filters: {
bookingUid: preSelectedBookingUid,
statuses: [...validStatuses],
},
},
{
enabled: !!preSelectedBookingUid,
staleTime: 5 * 60 * 1000,
}
);
const preSelectedBookingFull = preSelectedBookingData?.bookings?.[0] ?? null;
const preSelectedBooking: BookingForTabResolution | null = preSelectedBookingFull;
return { preSelectedBookingUid, preSelectedBooking, preSelectedBookingFull, isPending };
}