feat: added rescheduled UI to Booking page and Booking Listing page (#20401)

* feat: add rescheduling to UI

- Introduced displaying the original booking information.
- Updated UI components to show rescheduling status and details.

* fix: update link styling in booking single view

- Changed link text size to small for better UI consistency in the booking details section.

* refactor, type fixes

* added missing margin

* added rescheduled under other badges

* optimised getting booking

* updated namings and perf

---------

Co-authored-by: amrit <iamamrit27@gmail.com>
This commit is contained in:
Gaurav Vermaandamrit authored and GitHub committed 2025-04-09 15:21:26 -04:00
1 parent 5ae50f8670
commit d3d4a90776
7 files changed
+69 -1

No files matched your search

@@ -163,6 +163,7 @@ function BookingListItem(booking: BookingItemProps) {
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
const isRejected = booking.status === BookingStatus.REJECTED;
const isPending = booking.status === BookingStatus.PENDING;
const isRescheduled = booking.fromReschedule !== null;
const isRecurring = booking.recurringEventId !== null;
const isTabRecurring = booking.listingStatus === "recurring";
const isTabUnconfirmed = booking.listingStatus === "unconfirmed";
@@ -730,6 +731,7 @@ function BookingListItem(booking: BookingItemProps) {
recurringDates={recurringDates}
userTimeFormat={userTimeFormat}
userTimeZone={userTimeZone}
isRescheduled={isRescheduled}
/>
</div>
@@ -750,12 +752,14 @@ const BookingItemBadges = ({
recurringDates,
userTimeFormat,
userTimeZone,
isRescheduled,
}: {
booking: BookingItemProps;
isPending: boolean;
recurringDates: Date[] | undefined;
userTimeFormat: number | null | undefined;
userTimeZone: string | undefined;
isRescheduled: boolean;
}) => {
const { t } = useLocale();
@@ -766,6 +770,13 @@ const BookingItemBadges = ({
{t("unconfirmed")}
</Badge>
)}
{isRescheduled && (
<Tooltip content={`${t("rescheduled_by")} ${booking.rescheduler}`}>
<Badge variant="orange" className="ltr:mr-2 rtl:ml-2">
{t("rescheduled")}
</Badge>
</Tooltip>
)}
{booking.eventType?.team && (
<Badge className="ltr:mr-2 rtl:ml-2" variant="gray">
{booking.eventType.team.name}
@@ -82,6 +82,17 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
rescheduledToUid = rescheduledTo?.uid ?? null;
}
let previousBooking: {
rescheduledBy: string | null;
uid: string;
} | null = null;
if (bookingInfo.fromReschedule) {
previousBooking = await BookingRepository.findReschedulerByUid({
uid: bookingInfo.fromReschedule,
});
}
const eventTypeRaw = !bookingInfoRaw.eventTypeId
? getDefaultEvent(eventTypeSlug || "")
: await getEventTypesFromDB(bookingInfoRaw.eventTypeId);
@@ -220,6 +231,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId),
dynamicEventName: bookingInfo?.eventType?.eventName || "",
bookingInfo,
previousBooking,
paymentStatus: payment,
...(tz && { tz }),
userTimeFormat,
@@ -104,7 +104,9 @@ export default function Success(props: PageProps) {
const routerQuery = useRouterQuery();
const pathname = usePathname();
const searchParams = useCompatSearchParams();
const { eventType, bookingInfo, requiresLoginToUpdate, rescheduledToUid } = props;
const { eventType, bookingInfo, previousBooking, requiresLoginToUpdate, rescheduledToUid } = props;
const {
allRemainingBookings,
isSuccessBookingPage,
@@ -519,6 +521,17 @@ export default function Success(props: PageProps) {
<div className="col-span-2 mb-6 last:mb-0">{cancellationReason}</div>
</>
)}
{previousBooking && (
<>
<div className="font-medium">{t("rescheduled_by")}</div>
<div className="col-span-2 mb-6 last:mb-0">
<p className="break-words">{previousBooking?.rescheduledBy}</p>
<Link className="text-sm underline" href={`/booking/${previousBooking?.uid}`}>
{t("original_booking")}
</Link>
</div>
</>
)}
<div className="font-medium">{t("what")}</div>
<div className="col-span-2 mb-6 last:mb-0" data-testid="booking-title">
{isRoundRobin ? bookingInfo.title : eventName}
@@ -1361,6 +1361,8 @@
"custom": "Custom",
"reminder": "Reminder",
"rescheduled": "Rescheduled",
"rescheduled_by": "Rescheduled by",
"original_booking": "Original booking",
"completed": "Completed",
"rating": "Rating",
"reminder_email": "Reminder: {{eventType}} with {{name}} at {{date}}",
@@ -24,6 +24,7 @@ const getUserBooking = async (uid: string) => {
userPrimaryEmail: true,
fromReschedule: true,
rescheduled: true,
rescheduledBy: true,
user: {
select: {
id: true,
+12
View File
@@ -138,6 +138,18 @@ export class BookingRepository {
});
}
static async findReschedulerByUid({ uid }: { uid: string }) {
return await prisma.booking.findFirst({
where: {
uid,
},
select: {
rescheduledBy: true,
uid: true,
},
});
}
static async groupByActiveBookingCounts({
users,
eventTypeId,
@@ -132,6 +132,7 @@ export async function getBookings({
email: true,
},
},
fromReschedule: true,
rescheduled: true,
references: true,
isRecorded: true,
@@ -437,8 +438,24 @@ export async function getBookings({
booking.attendees = booking.attendees.filter((attendee) => attendee.email === user.email);
}
let rescheduler = null;
if (booking.fromReschedule) {
const rescheduledBooking = await prisma.booking.findUnique({
where: {
uid: booking.fromReschedule,
},
select: {
rescheduledBy: true,
},
});
if (rescheduledBooking) {
rescheduler = rescheduledBooking.rescheduledBy;
}
}
return {
...booking,
rescheduler,
eventType: {
...booking.eventType,
recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),