Skip to content

Commit 5084236

Browse files
authored
Merge pull request #1705 from rockingrohit9639/feature/adjust-booking-cta-wording
Feature/adjust booking cta wording
2 parents 93dc7e9 + f16d007 commit 5084236

File tree

9 files changed

+223
-42
lines changed

9 files changed

+223
-42
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Close } from "@radix-ui/react-dialog";
2+
import type { LucideIcon } from "lucide-react";
3+
import { ArrowLeft, ArrowRight, ClockIcon, InfoIcon } from "lucide-react";
4+
import { tw } from "~/utils/tw";
5+
import { XIcon } from "../icons/library";
6+
import { Button } from "../shared/button";
7+
import { Sheet, SheetContent, SheetTrigger } from "../shared/sheet";
8+
9+
type BookingProcessSidebarProps = {
10+
className?: string;
11+
};
12+
13+
type ProcessItem = {
14+
icon: LucideIcon;
15+
title: string;
16+
description: string;
17+
iconClassName: string;
18+
};
19+
20+
const ITEMS: Array<ProcessItem> = [
21+
{
22+
icon: ClockIcon,
23+
title: "Submit Request",
24+
description: `Fill in all required information and select the assets you need. Click "Request reservation" to submit your request.`,
25+
iconClassName: "bg-blue-100 text-blue-500",
26+
},
27+
{
28+
icon: InfoIcon,
29+
title: "Admin Review",
30+
description:
31+
"Your booking will be shown as reserved, however the admin can choose to revert it back to draft or cancel it at any point, if there are any conflicts with other bookings.",
32+
iconClassName: "bg-warning-100 text-warning-500",
33+
},
34+
{
35+
icon: ArrowRight,
36+
title: "Check-Out",
37+
description:
38+
"On the start date of your booking, an administrator will check out the equipment on your behalf. You'll be responsible for the equipment during your booking period.",
39+
iconClassName: "bg-violet-100 text-violet-500",
40+
},
41+
{
42+
icon: ArrowLeft,
43+
title: "Check-In",
44+
description:
45+
"At the end of you booking period, return the equipment to the administrator who will perform the check in action.",
46+
iconClassName: "bg-indigo-100 text-indigo-500",
47+
},
48+
];
49+
50+
export default function BookingProcessSidebar({
51+
className,
52+
}: BookingProcessSidebarProps) {
53+
return (
54+
<Sheet>
55+
<SheetTrigger asChild>
56+
<Button variant="block-link-gray" className={"mt-0"}>
57+
<div className="flex items-center gap-2">
58+
<InfoIcon className="size-4" />
59+
How bookings work
60+
</div>
61+
</Button>
62+
</SheetTrigger>
63+
64+
<SheetContent
65+
hideCloseButton
66+
className={tw("border-l-0 bg-white p-0", className)}
67+
>
68+
<div className="flex items-center justify-between bg-blue-500 p-4 text-white">
69+
<div className="flex items-center gap-2 text-lg font-bold">
70+
<InfoIcon className="size-4" />
71+
Booking Process
72+
</div>
73+
74+
<Close className="opacity-70 transition-opacity hover:opacity-100">
75+
<XIcon className="size-4" />
76+
<span className="sr-only">Close</span>
77+
</Close>
78+
</div>
79+
80+
<div className="p-4">
81+
<p className="mb-8 border-l-4 border-blue-500 bg-blue-50 p-2 text-blue-500">
82+
Base users reserve bookings that require admin approval and can be
83+
cancelled at any time if there are conflicts with other bookings.
84+
Admins handle equipment check-out and check-in.
85+
</p>
86+
87+
<div className="mb-8 flex flex-col gap-4">
88+
{ITEMS.map((item, i) => (
89+
<div key={i} className="flex items-start gap-4">
90+
<div
91+
className={tw(
92+
"flex items-center justify-center rounded-full p-4",
93+
item.iconClassName
94+
)}
95+
>
96+
{}
97+
<item.icon className="size-5" />
98+
</div>
99+
100+
<div>
101+
<h3 className="mb-1">
102+
{i + 1}. {item.title}
103+
</h3>
104+
<p>{item.description}</p>
105+
</div>
106+
</div>
107+
))}
108+
</div>
109+
110+
<div className="rounded-md bg-gray-50 p-4">
111+
<h3 className="mb-1">Important Notes</h3>
112+
113+
<ul className="list-inside list-disc">
114+
<li>
115+
Equipment must be returned in the same condition it was checked
116+
out.
117+
</li>
118+
<li>
119+
If you need to extend your booking, contact an administrator
120+
before your booking end date.
121+
</li>
122+
<li>
123+
Administrators have final say on booking approvals based on
124+
equipment availability and priorities.
125+
</li>
126+
</ul>
127+
</div>
128+
</div>
129+
</SheetContent>
130+
</Sheet>
131+
);
132+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { BookingStatus } from "@prisma/client";
2+
import { useUserRoleHelper } from "~/hooks/user-user-role-helper";
3+
import { bookingStatusColorMap } from "~/utils/bookings";
4+
import { Badge } from "../shared/badge";
5+
import {
6+
Tooltip,
7+
TooltipContent,
8+
TooltipProvider,
9+
TooltipTrigger,
10+
} from "../shared/tooltip";
11+
12+
export function BookingStatusBadge({ status }: { status: BookingStatus }) {
13+
const { isBase } = useUserRoleHelper();
14+
const shouldShowExtraInfo = isBase && status === BookingStatus.RESERVED;
15+
16+
return (
17+
<Badge color={bookingStatusColorMap[status]}>
18+
{shouldShowExtraInfo ? (
19+
<ExtraInfoTooltip>
20+
<span className="block whitespace-nowrap lowercase first-letter:uppercase">
21+
{status} - subject to review
22+
</span>
23+
</ExtraInfoTooltip>
24+
) : (
25+
<span className="block whitespace-nowrap lowercase first-letter:uppercase">
26+
{status}
27+
</span>
28+
)}
29+
</Badge>
30+
);
31+
}
32+
33+
function ExtraInfoTooltip({ children }: { children: React.ReactNode }) {
34+
return (
35+
<TooltipProvider>
36+
<Tooltip>
37+
<TooltipTrigger>{children}</TooltipTrigger>
38+
<TooltipContent side="top" className="max-w-72">
39+
<p>
40+
Your booking is currently reserved, however the admin can choose to
41+
reject or close it at any point of time, if there are conflicts with
42+
other bookings.
43+
</p>
44+
</TooltipContent>
45+
</Tooltip>
46+
</TooltipProvider>
47+
);
48+
}

app/components/booking/form.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { tw } from "~/utils/tw";
2626
import { resolveTeamMemberName } from "~/utils/user";
2727
import { ActionsDropdown } from "./actions-dropdown";
2828
import { Form } from "../custom-form";
29+
import BookingProcessSidebar from "./booking-process-sidebar";
2930
import DynamicSelect from "../dynamic-select/dynamic-select";
3031
import FormRow from "../forms/form-row";
3132
import Input from "../forms/input";
@@ -170,7 +171,7 @@ export function BookingForm({
170171
NewBookingFormSchema(inputFieldIsDisabled, isNewBooking)
171172
);
172173

173-
const { roles, isBaseOrSelfService } = useUserRoleHelper();
174+
const { roles, isBaseOrSelfService, isBase } = useUserRoleHelper();
174175

175176
const canCheckInBooking = userHasPermission({
176177
roles,
@@ -205,6 +206,10 @@ export function BookingForm({
205206
{/* Render the actions on top only when the form is in edit mode */}
206207
{!isNewBooking ? (
207208
<AbsolutePositionedHeaderActions>
209+
<When truthy={isBase}>
210+
<BookingProcessSidebar />
211+
</When>
212+
208213
{/* When the booking is Completed, there are no actions available for BASE role so we don't render it */}
209214
<ActionsDropdown />
210215

@@ -259,7 +264,7 @@ export function BookingForm({
259264
className="grow"
260265
size="sm"
261266
>
262-
Reserve
267+
{isBase ? "Request reservation" : "Reserve"}
263268
</Button>
264269
) : null}
265270

app/components/layout/header/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export type ButtonVariant =
5353
| "link-gray"
5454
| "block-link"
5555
| "block-link-gray"
56-
| "danger";
56+
| "danger"
57+
| "info";
5758

5859
/** Width of the button. Default is auto */
5960
export type ButtonWidth = "auto" | "full";

app/components/shared/button.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ const variants: Record<ButtonVariant, string> = {
124124
"disabled:border-error-300 disabled:bg-error-300",
125125
"enabled:hover:bg-error-800"
126126
),
127+
info: "bg-blue-500 text-white hover:bg-blue-400 focus:ring-2 disabled:bg-blue-300",
127128
};
128129

129130
/**

app/components/shared/sheet.tsx

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as SheetPrimitive from "@radix-ui/react-dialog";
33
import { cva, type VariantProps } from "class-variance-authority";
44
import { tw } from "~/utils/tw";
55
import { XIcon } from "../icons/library";
6+
import When from "../when/when";
67

78
const Sheet = SheetPrimitive.Root;
89

@@ -52,23 +53,31 @@ interface SheetContentProps
5253

5354
const SheetContent = forwardRef<
5455
React.ElementRef<typeof SheetPrimitive.Content>,
55-
SheetContentProps
56-
>(({ side = "right", className, children, ...props }, ref) => (
57-
<SheetPortal>
58-
<SheetOverlay />
59-
<SheetPrimitive.Content
60-
ref={ref}
61-
className={tw(sheetVariants({ side }), className)}
62-
{...props}
63-
>
64-
<SheetPrimitive.Close className="ring-offset-background data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
65-
<XIcon className="size-4" />
66-
<span className="sr-only">Close</span>
67-
</SheetPrimitive.Close>
68-
{children}
69-
</SheetPrimitive.Content>
70-
</SheetPortal>
71-
));
56+
SheetContentProps & { hideCloseButton?: boolean }
57+
>(
58+
(
59+
{ side = "right", className, children, hideCloseButton = false, ...props },
60+
ref
61+
) => (
62+
<SheetPortal>
63+
<SheetOverlay />
64+
<SheetPrimitive.Content
65+
ref={ref}
66+
className={tw(sheetVariants({ side }), className)}
67+
{...props}
68+
>
69+
<When truthy={!hideCloseButton}>
70+
<SheetPrimitive.Close className="ring-offset-background data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
71+
<XIcon className="size-4" />
72+
<span className="sr-only">Close</span>
73+
</SheetPrimitive.Close>
74+
</When>
75+
76+
{children}
77+
</SheetPrimitive.Content>
78+
</SheetPortal>
79+
)
80+
);
7281
SheetContent.displayName = SheetPrimitive.Content.displayName;
7382

7483
const SheetHeader = ({

app/routes/_layout+/bookings.$bookingId.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import { useAtomValue } from "jotai";
1010
import { DateTime } from "luxon";
1111
import { z } from "zod";
1212
import { dynamicTitleAtom } from "~/atoms/dynamic-title-atom";
13+
import { BookingStatusBadge } from "~/components/booking/booking-status-badge";
1314
import { NewBookingFormSchema } from "~/components/booking/form";
1415
import { BookingPageContent } from "~/components/booking/page-content";
1516
import ContextualModal from "~/components/layout/contextual-modal";
1617
import Header from "~/components/layout/header";
1718
import type { HeaderData } from "~/components/layout/header/types";
18-
import { Badge } from "~/components/shared/badge";
1919
import { db } from "~/database/db.server";
2020
import { hasGetAllValue } from "~/hooks/use-model-filters";
2121
import {
@@ -33,7 +33,6 @@ import { getTeamMemberForCustodianFilter } from "~/modules/team-member/service.s
3333
import type { RouteHandleWithName } from "~/modules/types";
3434
import { getUserByID } from "~/modules/user/service.server";
3535
import { appendToMetaTitle } from "~/utils/append-to-meta-title";
36-
import { bookingStatusColorMap } from "~/utils/bookings";
3736
import { checkExhaustiveSwitch } from "~/utils/check-exhaustive-switch";
3837
import { getClientHint, getHints } from "~/utils/client-hints";
3938
import {
@@ -589,7 +588,7 @@ export async function action({ context, request, params }: ActionFunctionArgs) {
589588
}
590589
}
591590

592-
export default function BookingEditPage() {
591+
export default function BookingPage() {
593592
const name = useAtomValue(dynamicTitleAtom);
594593
const hasName = name !== "";
595594
const { booking } = useLoaderData<typeof loader>();
@@ -608,11 +607,7 @@ export default function BookingEditPage() {
608607
title={hasName ? name : booking.name}
609608
subHeading={
610609
<div key={booking.status} className="flex items-center gap-2">
611-
<Badge color={bookingStatusColorMap[booking.status]}>
612-
<span className="block lowercase first-letter:uppercase">
613-
{booking.status}
614-
</span>
615-
</Badge>
610+
<BookingStatusBadge status={booking.status} />
616611
</div>
617612
}
618613
/>

app/routes/_layout+/bookings.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { ShouldRevalidateFunction } from "@remix-run/react";
66
import { Link, Outlet, useMatches, useNavigate } from "@remix-run/react";
77
import { ChevronRight } from "lucide-react";
88
import { AvailabilityBadge } from "~/components/booking/availability-label";
9+
import { BookingStatusBadge } from "~/components/booking/booking-status-badge";
910
import BulkActionsDropdown from "~/components/booking/bulk-actions-dropdown";
1011
import CreateBookingDialog from "~/components/booking/create-booking-dialog";
1112
import { ExportBookingsButton } from "~/components/booking/export-bookings-button";
@@ -21,7 +22,6 @@ import { List } from "~/components/list";
2122
import { ListContentWrapper } from "~/components/list/content-wrapper";
2223
import { Filters } from "~/components/list/filters";
2324
import { SortBy } from "~/components/list/filters/sort-by";
24-
import { Badge } from "~/components/shared/badge";
2525
import { Button } from "~/components/shared/button";
2626
import { Td, Th } from "~/components/table";
2727
import When from "~/components/when/when";
@@ -36,7 +36,6 @@ import { setSelectedOrganizationIdCookie } from "~/modules/organization/context.
3636
import { getTeamMemberForCustodianFilter } from "~/modules/team-member/service.server";
3737
import type { RouteHandleWithName } from "~/modules/types";
3838
import { appendToMetaTitle } from "~/utils/append-to-meta-title";
39-
import { bookingStatusColorMap } from "~/utils/bookings";
4039
import { setCookie, userPrefs } from "~/utils/cookies.server";
4140
import { makeShelfError, ShelfError } from "~/utils/error";
4241
import { data, error } from "~/utils/http.server";
@@ -376,11 +375,7 @@ const ListAssetContent = ({
376375
{item.name}
377376
</span>
378377
<div className="">
379-
<Badge color={bookingStatusColorMap[item.status]}>
380-
<span className="block lowercase first-letter:uppercase">
381-
{item.status}
382-
</span>
383-
</Badge>
378+
<BookingStatusBadge status={item.status} />
384379
</div>
385380
</div>
386381
</div>

0 commit comments

Comments
 (0)