@burdenoff/microfe-ggnomad 2026.1029.3 → 2026.1030.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-types.d.ts +102099 -35827
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/ggnomad/components/AuditTrailDialog.d.ts +6 -0
- package/dist/ggnomad/components/AuditTrailDialog.d.ts.map +1 -1
- package/dist/ggnomad/components/AuditTrailDialog.js +47 -42
- package/dist/ggnomad/components/AuditTrailDialog.js.map +1 -1
- package/dist/ggnomad/components/EntityCard.d.ts +9 -1
- package/dist/ggnomad/components/EntityCard.d.ts.map +1 -1
- package/dist/ggnomad/components/EntityCard.js +14 -7
- package/dist/ggnomad/components/EntityCard.js.map +1 -1
- package/dist/ggnomad/graphql.d.ts +6 -0
- package/dist/ggnomad/graphql.d.ts.map +1 -1
- package/dist/ggnomad/graphql.js +39 -18
- package/dist/ggnomad/graphql.js.map +1 -1
- package/dist/ggnomad/hooks/useFocusedRecordParam.d.ts +23 -0
- package/dist/ggnomad/hooks/useFocusedRecordParam.d.ts.map +1 -0
- package/dist/ggnomad/hooks/useFocusedRecordParam.js +15 -0
- package/dist/ggnomad/hooks/useFocusedRecordParam.js.map +1 -0
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +149 -142
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js +135 -134
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
- package/dist/services/assistantQuota.d.ts +69 -0
- package/dist/services/assistantQuota.d.ts.map +1 -0
- package/dist/services/assistantQuota.js +52 -0
- package/dist/services/assistantQuota.js.map +1 -0
- package/dist/services/createSandboxAssistantTransport.d.ts +5 -3
- package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +26 -6
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/utils/pageContext.d.ts +62 -8
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +114 -39
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +3 -2
|
@@ -2,39 +2,40 @@ import { CANCEL_GGNOMAD_BOOKING_MUTATION as e, CREATE_GGNOMAD_BOOKING_MUTATION a
|
|
|
2
2
|
import { formatDate as i, humanizeEnum as a } from "../utils/format.js";
|
|
3
3
|
import { PageShell as o } from "./PageShell.js";
|
|
4
4
|
import { cn as s } from "../utils/cn.js";
|
|
5
|
-
import { StatusBadge as
|
|
6
|
-
import { EntityCard as
|
|
7
|
-
import { notify as
|
|
8
|
-
import { TicketDialog as
|
|
9
|
-
import { RedeemTicketPanel as
|
|
5
|
+
import { StatusBadge as c } from "../components/StatusBadge.js";
|
|
6
|
+
import { EntityCard as l } from "../components/EntityCard.js";
|
|
7
|
+
import { notify as u } from "../utils/notify.js";
|
|
8
|
+
import { TicketDialog as d } from "../components/TicketDialog.js";
|
|
9
|
+
import { RedeemTicketPanel as f } from "../components/RedeemTicketPanel.js";
|
|
10
10
|
import "../components/index.js";
|
|
11
|
-
import { stayDates as
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
11
|
+
import { stayDates as p } from "../utils/bookingDates.js";
|
|
12
|
+
import { useFocusedRecordParam as m } from "../hooks/useFocusedRecordParam.js";
|
|
13
|
+
import { useMemo as h, useState as g } from "react";
|
|
14
|
+
import { CalendarCheck as _, CalendarRange as ee, Compass as te, Hotel as v, Leaf as ne, Plus as re, QrCode as y, Ticket as b, Users as x, XCircle as S } from "lucide-react";
|
|
15
|
+
import { useI18n as ie } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
16
|
+
import { useNavigate as C } from "react-router";
|
|
17
|
+
import { useMutation as w, useQuery as T } from "@apollo/client/react";
|
|
18
|
+
import { AlertDialog as E, AlertDialogAction as D, AlertDialogCancel as ae, AlertDialogContent as oe, AlertDialogDescription as O, AlertDialogFooter as k, AlertDialogHeader as A, AlertDialogTitle as j, CTAOverflowMenu as M, GlassCard as N, IllustratedEmptyState as P, NextSteps as F, PagePurpose as I } from "@burdenoff/fe-libs/ui";
|
|
19
|
+
import { jsx as L, jsxs as R } from "react/jsx-runtime";
|
|
19
20
|
//#region src/ggnomad/pages/BookingsPage.tsx
|
|
20
|
-
function
|
|
21
|
-
let { t:
|
|
21
|
+
function z() {
|
|
22
|
+
let { t: z } = ie(), B = C(), [V, H] = g(null), [U, W] = g(null), [G, K] = g(null), [q, J] = m(), { data: Y, refetch: X } = T(r, { variables: {
|
|
22
23
|
limit: 20,
|
|
23
24
|
offset: 0,
|
|
24
|
-
status:
|
|
25
|
-
} }), { data:
|
|
25
|
+
status: G ?? void 0
|
|
26
|
+
} }), { data: Z } = T(n), [se, { loading: ce }] = w(t), [le, { loading: Q }] = w(e), $ = h(() => Y?.ggnomadBookings ?? [], [Y?.ggnomadBookings]), ue = h(() => {
|
|
26
27
|
let e = {
|
|
27
28
|
CONFIRMED: 0,
|
|
28
29
|
CHECKED_IN: 0,
|
|
29
30
|
COMPLETED: 0,
|
|
30
31
|
CANCELLED: 0
|
|
31
32
|
};
|
|
32
|
-
return
|
|
33
|
+
return $.forEach((t) => {
|
|
33
34
|
let n = t.status?.toUpperCase();
|
|
34
35
|
n && n in e && (e[n] += 1);
|
|
35
36
|
}), e;
|
|
36
|
-
}, [
|
|
37
|
-
let e =
|
|
37
|
+
}, [$]), de = () => {
|
|
38
|
+
let e = Z?.ggnomadProperties?.[0];
|
|
38
39
|
if (e) return {
|
|
39
40
|
type: "PROPERTY",
|
|
40
41
|
listing: {
|
|
@@ -44,152 +45,152 @@ function B() {
|
|
|
44
45
|
currency: e.currency
|
|
45
46
|
}
|
|
46
47
|
};
|
|
47
|
-
let t =
|
|
48
|
+
let t = Z?.ggnomadTours?.[0];
|
|
48
49
|
if (t) return {
|
|
49
50
|
type: "TOUR",
|
|
50
51
|
listing: t
|
|
51
52
|
};
|
|
52
|
-
let n =
|
|
53
|
+
let n = Z?.ggnomadEvents?.[0];
|
|
53
54
|
if (n) return {
|
|
54
55
|
type: "EVENT",
|
|
55
56
|
listing: n
|
|
56
57
|
};
|
|
57
|
-
let r =
|
|
58
|
+
let r = Z?.ggnomadActivities?.[0];
|
|
58
59
|
return r ? {
|
|
59
60
|
type: "ACTIVITY",
|
|
60
61
|
listing: r
|
|
61
62
|
} : null;
|
|
62
|
-
},
|
|
63
|
-
let e =
|
|
63
|
+
}, fe = async () => {
|
|
64
|
+
let e = de();
|
|
64
65
|
if (!e) {
|
|
65
|
-
|
|
66
|
+
u.warning(z("ggnomad.bookings.toast.noListingsTitle", "No listings available to book yet"), z("ggnomad.bookings.toast.noListingsBody", "Add a property, tour, event or activity first, then create a booking against it."));
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
let { type: t, listing: n } = e;
|
|
69
70
|
try {
|
|
70
|
-
let e = await
|
|
71
|
+
let e = await se({ variables: { input: {
|
|
71
72
|
bookingType: t,
|
|
72
73
|
itemId: n.id,
|
|
73
74
|
itemName: n.name,
|
|
74
|
-
guestName:
|
|
75
|
+
guestName: z("ggnomad.bookings.demo.guestName", "Demo Guest"),
|
|
75
76
|
guestEmail: "demo@burdenoff.com",
|
|
76
77
|
guests: 2,
|
|
77
78
|
totalPrice: Number(n.price ?? 0),
|
|
78
79
|
currency: n.currency ?? "USD",
|
|
79
80
|
paymentMethod: "card",
|
|
80
|
-
...t === "PROPERTY" ?
|
|
81
|
+
...t === "PROPERTY" ? p(null, null) : {}
|
|
81
82
|
} } });
|
|
82
83
|
if (e.error) {
|
|
83
|
-
|
|
84
|
+
u.error(z("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
87
|
let r = e.data?.createGgnomadBooking;
|
|
87
|
-
|
|
88
|
+
u.success(z("ggnomad.bookings.toast.createSuccessTitle", "Booking confirmed"), r ? z("ggnomad.bookings.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : n.name), await X();
|
|
88
89
|
} catch (e) {
|
|
89
|
-
|
|
90
|
+
u.error(z("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
|
|
90
91
|
}
|
|
91
|
-
},
|
|
92
|
-
if (
|
|
93
|
-
let e = await
|
|
94
|
-
id:
|
|
95
|
-
reason:
|
|
92
|
+
}, pe = async () => {
|
|
93
|
+
if (V) try {
|
|
94
|
+
let e = await le({ variables: {
|
|
95
|
+
id: V.id,
|
|
96
|
+
reason: z("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
|
|
96
97
|
} });
|
|
97
98
|
if (e.error) {
|
|
98
|
-
|
|
99
|
+
u.error(z("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
+
u.success(z("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), V.referenceCode), await X();
|
|
102
103
|
} catch (e) {
|
|
103
|
-
|
|
104
|
+
u.error(z("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
|
|
104
105
|
} finally {
|
|
105
|
-
|
|
106
|
+
H(null);
|
|
106
107
|
}
|
|
107
108
|
};
|
|
108
|
-
return /* @__PURE__ */
|
|
109
|
-
title:
|
|
110
|
-
titleAccent:
|
|
111
|
-
subtitle:
|
|
112
|
-
actions: /* @__PURE__ */
|
|
109
|
+
return /* @__PURE__ */ R(o, {
|
|
110
|
+
title: z("ggnomad.bookings.title", "Bookings"),
|
|
111
|
+
titleAccent: z("ggnomad.bookings.title.accent", "Bookings"),
|
|
112
|
+
subtitle: z("ggnomad.bookings.subtitle", "End-to-end booking operations with real API mutations"),
|
|
113
|
+
actions: /* @__PURE__ */ L(M, {
|
|
113
114
|
primary: {
|
|
114
|
-
label:
|
|
115
|
-
onSelect: () => void
|
|
116
|
-
disabled:
|
|
117
|
-
icon: /* @__PURE__ */
|
|
115
|
+
label: z("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
|
|
116
|
+
onSelect: () => void fe(),
|
|
117
|
+
disabled: ce,
|
|
118
|
+
icon: /* @__PURE__ */ L(re, {})
|
|
118
119
|
},
|
|
119
120
|
actions: []
|
|
120
121
|
}),
|
|
121
122
|
children: [
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
icon: /* @__PURE__ */
|
|
124
|
-
children:
|
|
123
|
+
/* @__PURE__ */ L(I, {
|
|
124
|
+
icon: /* @__PURE__ */ L(_, { className: "size-4" }),
|
|
125
|
+
children: z("ggnomad.bookings.purpose", "Your single place to manage every reservation across travel, stays, tours and events. Track each booking’s status and reference code in real time, and cancel a reservation when plans change — all backed by live API mutations.")
|
|
125
126
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
+
/* @__PURE__ */ L(F, {
|
|
127
128
|
storageKey: "ggnomad-bookings",
|
|
128
129
|
steps: [
|
|
129
130
|
{
|
|
130
131
|
id: "book-stay",
|
|
131
|
-
label:
|
|
132
|
-
description:
|
|
133
|
-
icon: /* @__PURE__ */
|
|
134
|
-
onClick: () =>
|
|
132
|
+
label: z("ggnomad.bookings.next.stay", "Book a place to stay"),
|
|
133
|
+
description: z("ggnomad.bookings.next.stayDesc", "Browse hospitality listings and reserve a property."),
|
|
134
|
+
icon: /* @__PURE__ */ L(v, { className: "size-4" }),
|
|
135
|
+
onClick: () => B("../hospitality")
|
|
135
136
|
},
|
|
136
137
|
{
|
|
137
138
|
id: "book-tour",
|
|
138
|
-
label:
|
|
139
|
-
description:
|
|
140
|
-
icon: /* @__PURE__ */
|
|
141
|
-
onClick: () =>
|
|
139
|
+
label: z("ggnomad.bookings.next.tour", "Add a tour or activity"),
|
|
140
|
+
description: z("ggnomad.bookings.next.tourDesc", "Pick a curated experience from Tourism."),
|
|
141
|
+
icon: /* @__PURE__ */ L(te, { className: "size-4" }),
|
|
142
|
+
onClick: () => B("../tourism")
|
|
142
143
|
},
|
|
143
144
|
{
|
|
144
145
|
id: "book-event",
|
|
145
|
-
label:
|
|
146
|
-
description:
|
|
147
|
-
icon: /* @__PURE__ */
|
|
148
|
-
onClick: () =>
|
|
146
|
+
label: z("ggnomad.bookings.next.event", "Grab event tickets"),
|
|
147
|
+
description: z("ggnomad.bookings.next.eventDesc", "Find live events to round out the itinerary."),
|
|
148
|
+
icon: /* @__PURE__ */ L(b, { className: "size-4" }),
|
|
149
|
+
onClick: () => B("../entertainment")
|
|
149
150
|
}
|
|
150
151
|
]
|
|
151
152
|
}),
|
|
152
|
-
/* @__PURE__ */
|
|
153
|
-
|
|
153
|
+
/* @__PURE__ */ L(f, { onRedeemed: () => void X() }),
|
|
154
|
+
$.length > 0 || G ? /* @__PURE__ */ L("div", {
|
|
154
155
|
className: "grid gap-3 sm:grid-cols-4",
|
|
155
156
|
children: [
|
|
156
157
|
{
|
|
157
158
|
status: "CONFIRMED",
|
|
158
|
-
label:
|
|
159
|
-
icon:
|
|
159
|
+
label: z("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
|
|
160
|
+
icon: _
|
|
160
161
|
},
|
|
161
162
|
{
|
|
162
163
|
status: "CHECKED_IN",
|
|
163
|
-
label:
|
|
164
|
-
icon:
|
|
164
|
+
label: z("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
|
|
165
|
+
icon: v
|
|
165
166
|
},
|
|
166
167
|
{
|
|
167
168
|
status: "COMPLETED",
|
|
168
|
-
label:
|
|
169
|
-
icon:
|
|
169
|
+
label: z("ggnomad.bookings.pipeline.completed", "Completed"),
|
|
170
|
+
icon: b
|
|
170
171
|
},
|
|
171
172
|
{
|
|
172
173
|
status: "CANCELLED",
|
|
173
|
-
label:
|
|
174
|
-
icon:
|
|
174
|
+
label: z("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
|
|
175
|
+
icon: S
|
|
175
176
|
}
|
|
176
177
|
].map((e) => {
|
|
177
|
-
let t = e.icon, n =
|
|
178
|
-
return /* @__PURE__ */
|
|
178
|
+
let t = e.icon, n = G === e.status, r = ue[e.status];
|
|
179
|
+
return /* @__PURE__ */ R("button", {
|
|
179
180
|
type: "button",
|
|
180
181
|
"aria-pressed": n,
|
|
181
|
-
onClick: () =>
|
|
182
|
+
onClick: () => K(n ? null : e.status),
|
|
182
183
|
className: s("flex flex-col items-center gap-2 rounded-lg border p-3 text-center transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg", n ? "border-action-primary-bg bg-action-primary-bg/10" : "border-border-seam bg-bg-surface/40 hover:border-border-default"),
|
|
183
184
|
children: [
|
|
184
|
-
/* @__PURE__ */
|
|
185
|
+
/* @__PURE__ */ L("div", {
|
|
185
186
|
className: "flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary",
|
|
186
|
-
children: /* @__PURE__ */
|
|
187
|
+
children: /* @__PURE__ */ L(t, { className: "size-4" })
|
|
187
188
|
}),
|
|
188
|
-
/* @__PURE__ */
|
|
189
|
+
/* @__PURE__ */ L("p", {
|
|
189
190
|
className: "text-xs font-medium text-text-secondary",
|
|
190
191
|
children: e.label
|
|
191
192
|
}),
|
|
192
|
-
/* @__PURE__ */
|
|
193
|
+
/* @__PURE__ */ L("p", {
|
|
193
194
|
className: "text-lg font-semibold tabular-nums text-text-primary",
|
|
194
195
|
children: r
|
|
195
196
|
})
|
|
@@ -197,88 +198,94 @@ function B() {
|
|
|
197
198
|
}, e.status);
|
|
198
199
|
})
|
|
199
200
|
}) : null,
|
|
200
|
-
|
|
201
|
+
G ? /* @__PURE__ */ R("div", {
|
|
201
202
|
className: "flex flex-wrap items-center gap-3 text-sm",
|
|
202
|
-
children: [/* @__PURE__ */
|
|
203
|
+
children: [/* @__PURE__ */ L("span", {
|
|
203
204
|
className: "text-text-secondary",
|
|
204
|
-
children:
|
|
205
|
-
}), /* @__PURE__ */
|
|
205
|
+
children: z("ggnomad.bookings.filteredBy", "Showing {{status}} bookings only").replace("{{status}}", a(G).toLowerCase())
|
|
206
|
+
}), /* @__PURE__ */ L("button", {
|
|
206
207
|
type: "button",
|
|
207
|
-
onClick: () =>
|
|
208
|
+
onClick: () => K(null),
|
|
208
209
|
className: "font-medium text-action-primary-bg hover:underline",
|
|
209
|
-
children:
|
|
210
|
+
children: z("ggnomad.bookings.clearFilter", "Show all bookings")
|
|
210
211
|
})]
|
|
211
212
|
}) : null,
|
|
212
|
-
|
|
213
|
+
$.length === 0 ? /* @__PURE__ */ L(P, {
|
|
213
214
|
illustration: "empty-data",
|
|
214
|
-
title:
|
|
215
|
-
description:
|
|
216
|
-
}) : /* @__PURE__ */
|
|
215
|
+
title: G ? z("ggnomad.bookings.noneForStatusTitle", "No {{status}} bookings").replace("{{status}}", a(G).toLowerCase()) : z("ggnomad.bookings.emptyTitle", "No bookings yet"),
|
|
216
|
+
description: G ? z("ggnomad.bookings.noneForStatusDescription", "Nothing is in this stage right now — clear the filter to see every booking.") : z("ggnomad.bookings.emptyDescription", "Create your first booking to see it tracked here in real time.")
|
|
217
|
+
}) : /* @__PURE__ */ R(N, {
|
|
217
218
|
treatment: "glass",
|
|
218
219
|
glow: !0,
|
|
219
220
|
className: "border-border-seam p-5",
|
|
220
|
-
children: [/* @__PURE__ */
|
|
221
|
+
children: [/* @__PURE__ */ L("h2", {
|
|
221
222
|
className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
|
|
222
|
-
children:
|
|
223
|
-
}), /* @__PURE__ */
|
|
223
|
+
children: z("ggnomad.bookings.activeBookings", "Your bookings")
|
|
224
|
+
}), /* @__PURE__ */ L("div", {
|
|
224
225
|
className: "grid gap-3 sm:grid-cols-2",
|
|
225
|
-
children:
|
|
226
|
+
children: $.map((e) => {
|
|
226
227
|
let t = i(e.checkIn), n = i(e.checkOut), r = i(e.eventDate), o = t ? n ? `${t} – ${n}` : t : r;
|
|
227
|
-
return /* @__PURE__ */
|
|
228
|
+
return /* @__PURE__ */ L(l, {
|
|
229
|
+
selected: q === e.id,
|
|
230
|
+
onSelect: () => J(q === e.id ? null : e.id),
|
|
228
231
|
title: e.referenceCode,
|
|
229
232
|
meta: e.itemName,
|
|
230
233
|
price: `${e.currency} ${Number(e.totalPrice).toFixed(2)}`,
|
|
231
|
-
badge: /* @__PURE__ */
|
|
234
|
+
badge: /* @__PURE__ */ L(c, {
|
|
232
235
|
status: e.status,
|
|
233
236
|
live: e.status !== "CANCELLED"
|
|
234
237
|
}),
|
|
235
|
-
footer: /* @__PURE__ */
|
|
238
|
+
footer: /* @__PURE__ */ R("div", {
|
|
236
239
|
className: "space-y-2",
|
|
237
|
-
children: [/* @__PURE__ */
|
|
240
|
+
children: [/* @__PURE__ */ R("div", {
|
|
238
241
|
className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary",
|
|
239
242
|
children: [
|
|
240
|
-
/* @__PURE__ */
|
|
241
|
-
o ? /* @__PURE__ */
|
|
243
|
+
/* @__PURE__ */ L("span", { children: a(e.bookingType) }),
|
|
244
|
+
o ? /* @__PURE__ */ R("span", {
|
|
242
245
|
className: "inline-flex items-center gap-1",
|
|
243
|
-
children: [/* @__PURE__ */
|
|
246
|
+
children: [/* @__PURE__ */ L(ee, {
|
|
244
247
|
className: "size-3.5",
|
|
245
248
|
"aria-hidden": "true"
|
|
246
249
|
}), o]
|
|
247
250
|
}) : null,
|
|
248
|
-
/* @__PURE__ */
|
|
251
|
+
/* @__PURE__ */ R("span", {
|
|
249
252
|
className: "inline-flex items-center gap-1 tabular-nums",
|
|
250
|
-
children: [/* @__PURE__ */
|
|
253
|
+
children: [/* @__PURE__ */ L(x, {
|
|
251
254
|
className: "size-3.5",
|
|
252
255
|
"aria-hidden": "true"
|
|
253
|
-
}), e.guests === 1 ?
|
|
256
|
+
}), e.guests === 1 ? z("ggnomad.bookings.oneGuest", "1 guest") : z("ggnomad.bookings.guests", "{{n}} guests").replace("{{n}}", String(e.guests))]
|
|
254
257
|
}),
|
|
255
|
-
/* @__PURE__ */
|
|
256
|
-
e.carbonEstimateKg !== null && e.carbonEstimateKg !== void 0 ? /* @__PURE__ */
|
|
258
|
+
/* @__PURE__ */ L("span", { children: z("ggnomad.bookings.payment", "Payment: {{status}}").replace("{{status}}", a(e.paymentStatus).toLowerCase()) }),
|
|
259
|
+
e.carbonEstimateKg !== null && e.carbonEstimateKg !== void 0 ? /* @__PURE__ */ R("span", {
|
|
257
260
|
className: "inline-flex items-center gap-1 tabular-nums text-status-success-text",
|
|
258
|
-
children: [/* @__PURE__ */
|
|
261
|
+
children: [/* @__PURE__ */ L(ne, {
|
|
259
262
|
className: "size-3.5",
|
|
260
263
|
"aria-hidden": "true"
|
|
261
|
-
}),
|
|
264
|
+
}), z("ggnomad.bookings.carbonEstimate", "≈{{kg}} kg CO₂e est.").replace("{{kg}}", e.carbonEstimateKg.toLocaleString())]
|
|
262
265
|
}) : null
|
|
263
266
|
]
|
|
264
|
-
}), e.status === "CANCELLED" ? null : /* @__PURE__ */
|
|
267
|
+
}), e.status === "CANCELLED" ? null : /* @__PURE__ */ L(M, {
|
|
265
268
|
align: "end",
|
|
266
269
|
actions: [{
|
|
267
|
-
label:
|
|
268
|
-
onSelect: () =>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
270
|
+
label: z("ggnomad.bookings.actions.viewTicket", "View mobile ticket"),
|
|
271
|
+
onSelect: () => {
|
|
272
|
+
J(e.id), W({
|
|
273
|
+
id: e.id,
|
|
274
|
+
referenceCode: e.referenceCode,
|
|
275
|
+
status: e.status
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
icon: /* @__PURE__ */ L(y, {})
|
|
274
279
|
}, {
|
|
275
|
-
label:
|
|
276
|
-
onSelect: () =>
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
label: z("ggnomad.bookings.actions.cancel", "Cancel booking"),
|
|
281
|
+
onSelect: () => {
|
|
282
|
+
J(e.id), H({
|
|
283
|
+
id: e.id,
|
|
284
|
+
referenceCode: e.referenceCode
|
|
285
|
+
});
|
|
286
|
+
},
|
|
280
287
|
intent: "destructive",
|
|
281
|
-
icon: /* @__PURE__ */
|
|
288
|
+
icon: /* @__PURE__ */ L(S, {})
|
|
282
289
|
}]
|
|
283
290
|
})]
|
|
284
291
|
})
|
|
@@ -286,36 +293,36 @@ function B() {
|
|
|
286
293
|
})
|
|
287
294
|
})]
|
|
288
295
|
}),
|
|
289
|
-
/* @__PURE__ */
|
|
290
|
-
open:
|
|
296
|
+
/* @__PURE__ */ L(E, {
|
|
297
|
+
open: V !== null,
|
|
291
298
|
onOpenChange: (e) => {
|
|
292
|
-
e ||
|
|
299
|
+
e || H(null);
|
|
293
300
|
},
|
|
294
|
-
children: /* @__PURE__ */
|
|
295
|
-
disabled:
|
|
296
|
-
children:
|
|
297
|
-
}), /* @__PURE__ */
|
|
301
|
+
children: /* @__PURE__ */ R(oe, { children: [/* @__PURE__ */ R(A, { children: [/* @__PURE__ */ L(j, { children: z("ggnomad.bookings.cancelDialog.title", "Cancel this booking?") }), /* @__PURE__ */ L(O, { children: z("ggnomad.bookings.cancelDialog.body", "This releases reservation {{ref}} and cannot be undone.").replace("{{ref}}", V?.referenceCode ?? "") })] }), /* @__PURE__ */ R(k, { children: [/* @__PURE__ */ L(ae, {
|
|
302
|
+
disabled: Q,
|
|
303
|
+
children: z("ggnomad.bookings.cancelDialog.keep", "Keep booking")
|
|
304
|
+
}), /* @__PURE__ */ L(D, {
|
|
298
305
|
className: "bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover",
|
|
299
|
-
disabled:
|
|
306
|
+
disabled: Q,
|
|
300
307
|
onClick: (e) => {
|
|
301
|
-
e.preventDefault(),
|
|
308
|
+
e.preventDefault(), pe();
|
|
302
309
|
},
|
|
303
|
-
children:
|
|
310
|
+
children: Q ? z("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : z("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
|
|
304
311
|
})] })] })
|
|
305
312
|
}),
|
|
306
|
-
|
|
307
|
-
open:
|
|
313
|
+
U ? /* @__PURE__ */ L(d, {
|
|
314
|
+
open: U !== null,
|
|
308
315
|
onOpenChange: (e) => {
|
|
309
|
-
e ||
|
|
316
|
+
e || W(null);
|
|
310
317
|
},
|
|
311
|
-
bookingId:
|
|
312
|
-
referenceCode:
|
|
313
|
-
bookingStatus:
|
|
318
|
+
bookingId: U.id,
|
|
319
|
+
referenceCode: U.referenceCode,
|
|
320
|
+
bookingStatus: U.status
|
|
314
321
|
}) : null
|
|
315
322
|
]
|
|
316
323
|
});
|
|
317
324
|
}
|
|
318
325
|
//#endregion
|
|
319
|
-
export {
|
|
326
|
+
export { z as BookingsPage };
|
|
320
327
|
|
|
321
328
|
//# sourceMappingURL=BookingsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BookingsPage.js","names":[],"sources":["../../../src/ggnomad/pages/BookingsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n CalendarCheck,\n CalendarRange,\n Compass,\n Hotel,\n Leaf,\n Plus,\n QrCode,\n Ticket,\n Users,\n XCircle,\n} from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n CTAOverflowMenu,\n GlassCard,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n CANCEL_GGNOMAD_BOOKING_MUTATION,\n CREATE_GGNOMAD_BOOKING_MUTATION,\n GGNOMAD_BOOKABLE_SAMPLE_QUERY,\n GGNOMAD_BOOKINGS_QUERY,\n} from '../graphql';\nimport { formatDate, humanizeEnum } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport { EntityCard, RedeemTicketPanel, StatusBadge, TicketDialog } from '../components';\nimport { notify } from '../utils/notify';\nimport { cn } from '../utils/cn';\nimport { stayDates } from '../utils/bookingDates';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\n/** Every value of `GgnomadBookingStatus`, the argument `ggnomadBookings` accepts. */\ntype BookingStatusFilter =\n 'PENDING' | 'CONFIRMED' | 'CHECKED_IN' | 'COMPLETED' | 'CANCELLED' | 'NO_SHOW' | 'REFUNDED';\n\ntype BookingRow = {\n id: string;\n referenceCode: string;\n bookingType: string;\n itemName: string;\n status: string;\n guestName: string;\n guests: number;\n checkIn?: string | null;\n checkOut?: string | null;\n eventDate?: string | null;\n totalPrice: number;\n currency: string;\n paymentStatus: string;\n carbonEstimateKg?: number | null;\n createdAt: string;\n};\n\ntype PricedListing = { id: string; name: string; price: number; currency: string };\n\ntype CancelTarget = { id: string; referenceCode: string };\n\ntype TicketTarget = { id: string; referenceCode: string; status: string };\n\nexport function BookingsPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const [cancelTarget, setCancelTarget] = useState<CancelTarget | null>(null);\n const [ticketTarget, setTicketTarget] = useState<TicketTarget | null>(null);\n const [statusFilter, setStatusFilter] = useState<BookingStatusFilter | null>(null);\n\n const { data, refetch } = useQuery(\n GGNOMAD_BOOKINGS_QUERY as TypedDocumentNode<\n { ggnomadBookings: BookingRow[] },\n { limit: number; offset: number; status?: BookingStatusFilter }\n >,\n { variables: { limit: 20, offset: 0, status: statusFilter ?? undefined } }\n );\n\n // One real listing of each type so the demo booking targets an id that\n // actually exists in the workspace (the backend rejects synthetic ids).\n const { data: sampleData } = useQuery(\n GGNOMAD_BOOKABLE_SAMPLE_QUERY as TypedDocumentNode<\n {\n ggnomadProperties: Array<{\n id: string;\n name: string;\n pricePerNight: number;\n currency: string;\n }>;\n ggnomadTours: Array<{ id: string; name: string; price: number; currency: string }>;\n ggnomadEvents: Array<{ id: string; name: string; price: number; currency: string }>;\n ggnomadActivities: Array<{ id: string; name: string; price: number; currency: string }>;\n },\n Record<string, never>\n >\n );\n\n const [createBooking, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { createGgnomadBooking: { id: string; referenceCode: string; itemName: string } },\n {\n input: {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n guestName: string;\n guestEmail: string;\n guests: number;\n totalPrice: number;\n currency: string;\n paymentMethod: string;\n checkIn?: string;\n checkOut?: string;\n };\n }\n >\n );\n\n const [cancelBooking, { loading: cancelling }] = useMutation(\n CANCEL_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { cancelGgnomadBooking: { id: string; status: string } },\n { id: string; reason: string }\n >\n );\n\n const bookings = useMemo(() => data?.ggnomadBookings ?? [], [data?.ggnomadBookings]);\n\n const statusCounts = useMemo(() => {\n const counts: Record<string, number> = {\n CONFIRMED: 0,\n CHECKED_IN: 0,\n COMPLETED: 0,\n CANCELLED: 0,\n };\n bookings.forEach((booking) => {\n const status = booking.status?.toUpperCase();\n if (status && status in counts) {\n counts[status] += 1;\n }\n });\n return counts;\n }, [bookings]);\n\n /** First available real listing, in priority order, mapped to a booking type. */\n const resolveDemoTarget = (): { type: BookingType; listing: PricedListing } | null => {\n const property = sampleData?.ggnomadProperties?.[0];\n if (property) {\n return {\n type: 'PROPERTY',\n listing: {\n id: property.id,\n name: property.name,\n price: property.pricePerNight,\n currency: property.currency,\n },\n };\n }\n const tour = sampleData?.ggnomadTours?.[0];\n if (tour) return { type: 'TOUR', listing: tour };\n const event = sampleData?.ggnomadEvents?.[0];\n if (event) return { type: 'EVENT', listing: event };\n const activity = sampleData?.ggnomadActivities?.[0];\n if (activity) return { type: 'ACTIVITY', listing: activity };\n return null;\n };\n\n const handleCreateDemoBooking = async () => {\n const target = resolveDemoTarget();\n if (!target) {\n notify.warning(\n t('ggnomad.bookings.toast.noListingsTitle', 'No listings available to book yet'),\n t(\n 'ggnomad.bookings.toast.noListingsBody',\n 'Add a property, tour, event or activity first, then create a booking against it.'\n )\n );\n return;\n }\n const { type, listing } = target;\n try {\n const result = await createBooking({\n variables: {\n input: {\n bookingType: type,\n itemId: listing.id,\n itemName: listing.name,\n guestName: t('ggnomad.bookings.demo.guestName', 'Demo Guest'),\n guestEmail: 'demo@burdenoff.com',\n guests: 2,\n totalPrice: Number(listing.price ?? 0),\n currency: listing.currency ?? 'USD',\n paymentMethod: 'card',\n // The service prices a stay per night (BOFF-10193), so the demo\n // stay is one night a week from today.\n ...(type === 'PROPERTY' ? stayDates(null, null) : {}),\n },\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.bookings.toast.createErrorTitle', 'Could not create booking'),\n result.error.message\n );\n return;\n }\n const created = result.data?.createGgnomadBooking;\n notify.success(\n t('ggnomad.bookings.toast.createSuccessTitle', 'Booking confirmed'),\n created\n ? t('ggnomad.bookings.toast.createSuccessBody', '{{ref}} · {{item}}')\n .replace('{{ref}}', created.referenceCode)\n .replace('{{item}}', created.itemName)\n : listing.name\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.bookings.toast.createErrorTitle', 'Could not create booking'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const confirmCancel = async () => {\n if (!cancelTarget) return;\n try {\n const result = await cancelBooking({\n variables: {\n id: cancelTarget.id,\n reason: t('ggnomad.bookings.actions.cancelReason', 'Cancelled via UI'),\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.bookings.toast.cancelErrorTitle', 'Could not cancel booking'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.bookings.toast.cancelSuccessTitle', 'Booking cancelled'),\n cancelTarget.referenceCode\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.bookings.toast.cancelErrorTitle', 'Could not cancel booking'),\n err instanceof Error ? err.message : String(err)\n );\n } finally {\n setCancelTarget(null);\n }\n };\n\n return (\n <PageShell\n title={t('ggnomad.bookings.title', 'Bookings')}\n titleAccent={t('ggnomad.bookings.title.accent', 'Bookings')}\n subtitle={t(\n 'ggnomad.bookings.subtitle',\n 'End-to-end booking operations with real API mutations'\n )}\n actions={\n <CTAOverflowMenu\n primary={{\n label: t('ggnomad.bookings.actions.createDemoBooking', 'Create demo booking'),\n onSelect: () => void handleCreateDemoBooking(),\n disabled: creating,\n icon: <Plus />,\n }}\n actions={[]}\n />\n }\n >\n <PagePurpose icon={<CalendarCheck className=\"size-4\" />}>\n {t(\n 'ggnomad.bookings.purpose',\n 'Your single place to manage every reservation across travel, stays, tours and events. Track each booking’s status and reference code in real time, and cancel a reservation when plans change — all backed by live API mutations.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"ggnomad-bookings\"\n steps={[\n {\n id: 'book-stay',\n label: t('ggnomad.bookings.next.stay', 'Book a place to stay'),\n description: t(\n 'ggnomad.bookings.next.stayDesc',\n 'Browse hospitality listings and reserve a property.'\n ),\n icon: <Hotel className=\"size-4\" />,\n onClick: () => navigate('../hospitality'),\n },\n {\n id: 'book-tour',\n label: t('ggnomad.bookings.next.tour', 'Add a tour or activity'),\n description: t(\n 'ggnomad.bookings.next.tourDesc',\n 'Pick a curated experience from Tourism.'\n ),\n icon: <Compass className=\"size-4\" />,\n onClick: () => navigate('../tourism'),\n },\n {\n id: 'book-event',\n label: t('ggnomad.bookings.next.event', 'Grab event tickets'),\n description: t(\n 'ggnomad.bookings.next.eventDesc',\n 'Find live events to round out the itinerary.'\n ),\n icon: <Ticket className=\"size-4\" />,\n onClick: () => navigate('../entertainment'),\n },\n ]}\n />\n\n <RedeemTicketPanel onRedeemed={() => void refetch()} />\n\n {bookings.length > 0 || statusFilter ? (\n <div className=\"grid gap-3 sm:grid-cols-4\">\n {[\n {\n status: 'CONFIRMED',\n label: t('ggnomad.bookings.pipeline.confirmed', 'Confirmed'),\n icon: CalendarCheck,\n },\n {\n status: 'CHECKED_IN',\n label: t('ggnomad.bookings.pipeline.checkedIn', 'Checked-in'),\n icon: Hotel,\n },\n {\n status: 'COMPLETED',\n label: t('ggnomad.bookings.pipeline.completed', 'Completed'),\n icon: Ticket,\n },\n {\n status: 'CANCELLED',\n label: t('ggnomad.bookings.pipeline.cancelled', 'Cancelled'),\n icon: XCircle,\n },\n ].map((stage) => {\n const Icon = stage.icon;\n const selected = statusFilter === stage.status;\n const count = statusCounts[stage.status];\n return (\n <button\n key={stage.status}\n type=\"button\"\n aria-pressed={selected}\n onClick={() =>\n setStatusFilter(selected ? null : (stage.status as BookingStatusFilter))\n }\n className={cn(\n 'flex flex-col items-center gap-2 rounded-lg border p-3 text-center transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg',\n selected\n ? 'border-action-primary-bg bg-action-primary-bg/10'\n : 'border-border-seam bg-bg-surface/40 hover:border-border-default'\n )}\n >\n <div className=\"flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"size-4\" />\n </div>\n <p className=\"text-xs font-medium text-text-secondary\">{stage.label}</p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">{count}</p>\n </button>\n );\n })}\n </div>\n ) : null}\n\n {statusFilter ? (\n <div className=\"flex flex-wrap items-center gap-3 text-sm\">\n <span className=\"text-text-secondary\">\n {t('ggnomad.bookings.filteredBy', 'Showing {{status}} bookings only').replace(\n '{{status}}',\n humanizeEnum(statusFilter).toLowerCase()\n )}\n </span>\n <button\n type=\"button\"\n onClick={() => setStatusFilter(null)}\n className=\"font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.bookings.clearFilter', 'Show all bookings')}\n </button>\n </div>\n ) : null}\n\n {bookings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n statusFilter\n ? t('ggnomad.bookings.noneForStatusTitle', 'No {{status}} bookings').replace(\n '{{status}}',\n humanizeEnum(statusFilter).toLowerCase()\n )\n : t('ggnomad.bookings.emptyTitle', 'No bookings yet')\n }\n description={\n statusFilter\n ? t(\n 'ggnomad.bookings.noneForStatusDescription',\n 'Nothing is in this stage right now — clear the filter to see every booking.'\n )\n : t(\n 'ggnomad.bookings.emptyDescription',\n 'Create your first booking to see it tracked here in real time.'\n )\n }\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.bookings.activeBookings', 'Your bookings')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {bookings.map((booking) => {\n const checkIn = formatDate(booking.checkIn);\n const checkOut = formatDate(booking.checkOut);\n const eventDate = formatDate(booking.eventDate);\n const dateLabel = checkIn\n ? checkOut\n ? `${checkIn} – ${checkOut}`\n : checkIn\n : eventDate;\n return (\n <EntityCard\n key={booking.id}\n title={booking.referenceCode}\n meta={booking.itemName}\n price={`${booking.currency} ${Number(booking.totalPrice).toFixed(2)}`}\n badge={\n <StatusBadge status={booking.status} live={booking.status !== 'CANCELLED'} />\n }\n footer={\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary\">\n <span>{humanizeEnum(booking.bookingType)}</span>\n {dateLabel ? (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarRange className=\"size-3.5\" aria-hidden=\"true\" />\n {dateLabel}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Users className=\"size-3.5\" aria-hidden=\"true\" />\n {booking.guests === 1\n ? t('ggnomad.bookings.oneGuest', '1 guest')\n : t('ggnomad.bookings.guests', '{{n}} guests').replace(\n '{{n}}',\n String(booking.guests)\n )}\n </span>\n <span>\n {t('ggnomad.bookings.payment', 'Payment: {{status}}').replace(\n '{{status}}',\n humanizeEnum(booking.paymentStatus).toLowerCase()\n )}\n </span>\n {booking.carbonEstimateKg !== null &&\n booking.carbonEstimateKg !== undefined ? (\n <span className=\"inline-flex items-center gap-1 tabular-nums text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.bookings.carbonEstimate', '≈{{kg}} kg CO₂e est.').replace(\n '{{kg}}',\n booking.carbonEstimateKg.toLocaleString()\n )}\n </span>\n ) : null}\n </div>\n {booking.status !== 'CANCELLED' ? (\n <CTAOverflowMenu\n align=\"end\"\n actions={[\n {\n label: t('ggnomad.bookings.actions.viewTicket', 'View mobile ticket'),\n onSelect: () =>\n setTicketTarget({\n id: booking.id,\n referenceCode: booking.referenceCode,\n status: booking.status,\n }),\n icon: <QrCode />,\n },\n {\n label: t('ggnomad.bookings.actions.cancel', 'Cancel booking'),\n onSelect: () =>\n setCancelTarget({\n id: booking.id,\n referenceCode: booking.referenceCode,\n }),\n intent: 'destructive',\n icon: <XCircle />,\n },\n ]}\n />\n ) : null}\n </div>\n }\n />\n );\n })}\n </div>\n </GlassCard>\n )}\n\n <AlertDialog\n open={cancelTarget !== null}\n onOpenChange={(open) => {\n if (!open) setCancelTarget(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {t('ggnomad.bookings.cancelDialog.title', 'Cancel this booking?')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t(\n 'ggnomad.bookings.cancelDialog.body',\n 'This releases reservation {{ref}} and cannot be undone.'\n ).replace('{{ref}}', cancelTarget?.referenceCode ?? '')}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={cancelling}>\n {t('ggnomad.bookings.cancelDialog.keep', 'Keep booking')}\n </AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover\"\n disabled={cancelling}\n onClick={(event) => {\n event.preventDefault();\n void confirmCancel();\n }}\n >\n {cancelling\n ? t('ggnomad.bookings.cancelDialog.cancelling', 'Cancelling…')\n : t('ggnomad.bookings.cancelDialog.confirm', 'Cancel booking')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n {ticketTarget ? (\n <TicketDialog\n open={ticketTarget !== null}\n onOpenChange={(open) => {\n if (!open) setTicketTarget(null);\n }}\n bookingId={ticketTarget.id}\n referenceCode={ticketTarget.referenceCode}\n bookingStatus={ticketTarget.status}\n />\n ) : null}\n </PageShell>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4EA,SAAgB,IAAe;CAC7B,IAAM,EAAE,SAAM,EAAQ,GAChB,IAAW,EAAY,GACvB,CAAC,GAAc,KAAmB,EAA8B,IAAI,GACpE,CAAC,GAAc,KAAmB,EAA8B,IAAI,GACpE,CAAC,GAAc,KAAmB,EAAqC,IAAI,GAE3E,EAAE,SAAM,eAAY,EACxB,GAIA,EAAE,WAAW;EAAE,OAAO;EAAI,QAAQ;EAAG,QAAQ,KAAgB,KAAA;CAAU,EAAE,CAC3E,GAIM,EAAE,MAAM,MAAe,EAC3B,CAcF,GAEM,CAAC,IAAe,EAAE,SAAS,QAAc,EAC7C,CAkBF,GAEM,CAAC,IAAe,EAAE,SAAS,OAAgB,EAC/C,CAIF,GAEM,IAAW,QAAc,GAAM,mBAAmB,CAAC,GAAG,CAAC,GAAM,eAAe,CAAC,GAE7E,KAAe,QAAc;EACjC,IAAM,IAAiC;GACrC,WAAW;GACX,YAAY;GACZ,WAAW;GACX,WAAW;EACb;EAOA,OANA,EAAS,SAAS,MAAY;GAC5B,IAAM,IAAS,EAAQ,QAAQ,YAAY;GAC3C,AAAI,KAAU,KAAU,MACtB,EAAO,MAAW;EAEtB,CAAC,GACM;CACT,GAAG,CAAC,CAAQ,CAAC,GAGP,WAAgF;EACpF,IAAM,IAAW,GAAY,oBAAoB;EACjD,IAAI,GACF,OAAO;GACL,MAAM;GACN,SAAS;IACP,IAAI,EAAS;IACb,MAAM,EAAS;IACf,OAAO,EAAS;IAChB,UAAU,EAAS;GACrB;EACF;EAEF,IAAM,IAAO,GAAY,eAAe;EACxC,IAAI,GAAM,OAAO;GAAE,MAAM;GAAQ,SAAS;EAAK;EAC/C,IAAM,IAAQ,GAAY,gBAAgB;EAC1C,IAAI,GAAO,OAAO;GAAE,MAAM;GAAS,SAAS;EAAM;EAClD,IAAM,IAAW,GAAY,oBAAoB;EAEjD,OADI,IAAiB;GAAE,MAAM;GAAY,SAAS;EAAS,IACpD;CACT,GAEM,IAA0B,YAAY;EAC1C,IAAM,IAAS,GAAkB;EACjC,IAAI,CAAC,GAAQ;GACX,EAAO,QACL,EAAE,0CAA0C,mCAAmC,GAC/E,EACE,yCACA,kFACF,CACF;GACA;EACF;EACA,IAAM,EAAE,SAAM,eAAY;EAC1B,IAAI;GACF,IAAM,IAAS,MAAM,GAAc,EACjC,WAAW,EACT,OAAO;IACL,aAAa;IACb,QAAQ,EAAQ;IAChB,UAAU,EAAQ;IAClB,WAAW,EAAE,mCAAmC,YAAY;IAC5D,YAAY;IACZ,QAAQ;IACR,YAAY,OAAO,EAAQ,SAAS,CAAC;IACrC,UAAU,EAAQ,YAAY;IAC9B,eAAe;IAGf,GAAI,MAAS,aAAa,EAAU,MAAM,IAAI,IAAI,CAAC;GACrD,EACF,EACF,CAAC;GACD,IAAI,EAAO,OAAO;IAChB,EAAO,MACL,EAAE,2CAA2C,0BAA0B,GACvE,EAAO,MAAM,OACf;IACA;GACF;GACA,IAAM,IAAU,EAAO,MAAM;GAS7B,AARA,EAAO,QACL,EAAE,6CAA6C,mBAAmB,GAClE,IACI,EAAE,4CAA4C,oBAAoB,CAAC,CAChE,QAAQ,WAAW,EAAQ,aAAa,CAAC,CACzC,QAAQ,YAAY,EAAQ,QAAQ,IACvC,EAAQ,IACd,GACA,MAAM,EAAQ;EAChB,SAAS,GAAK;GACZ,EAAO,MACL,EAAE,2CAA2C,0BAA0B,GACvE,aAAe,QAAQ,EAAI,UAAU,OAAO,CAAG,CACjD;EACF;CACF,GAEM,KAAgB,YAAY;EAC3B,OACL,IAAI;GACF,IAAM,IAAS,MAAM,GAAc,EACjC,WAAW;IACT,IAAI,EAAa;IACjB,QAAQ,EAAE,yCAAyC,kBAAkB;GACvE,EACF,CAAC;GACD,IAAI,EAAO,OAAO;IAChB,EAAO,MACL,EAAE,2CAA2C,0BAA0B,GACvE,EAAO,MAAM,OACf;IACA;GACF;GAKA,AAJA,EAAO,QACL,EAAE,6CAA6C,mBAAmB,GAClE,EAAa,aACf,GACA,MAAM,EAAQ;EAChB,SAAS,GAAK;GACZ,EAAO,MACL,EAAE,2CAA2C,0BAA0B,GACvE,aAAe,QAAQ,EAAI,UAAU,OAAO,CAAG,CACjD;EACF,UAAU;GACR,EAAgB,IAAI;EACtB;CACF;CAEA,OACE,kBAAC,GAAD;EACE,OAAO,EAAE,0BAA0B,UAAU;EAC7C,aAAa,EAAE,iCAAiC,UAAU;EAC1D,UAAU,EACR,6BACA,uDACF;EACA,SACE,kBAAC,GAAD;GACE,SAAS;IACP,OAAO,EAAE,8CAA8C,qBAAqB;IAC5E,gBAAgB,KAAK,EAAwB;IAC7C,UAAU;IACV,MAAM,kBAAC,GAAD,CAAO,CAAA;GACf;GACA,SAAS,CAAC;EACX,CAAA;YAhBL;GAmBE,kBAAC,GAAD;IAAa,MAAM,kBAAC,GAAD,EAAe,WAAU,SAAU,CAAA;cACnD,EACC,4BACA,mOACF;GACW,CAAA;GAEb,kBAAC,GAAD;IACE,YAAW;IACX,OAAO;KACL;MACE,IAAI;MACJ,OAAO,EAAE,8BAA8B,sBAAsB;MAC7D,aAAa,EACX,kCACA,qDACF;MACA,MAAM,kBAAC,GAAD,EAAO,WAAU,SAAU,CAAA;MACjC,eAAe,EAAS,gBAAgB;KAC1C;KACA;MACE,IAAI;MACJ,OAAO,EAAE,8BAA8B,wBAAwB;MAC/D,aAAa,EACX,kCACA,yCACF;MACA,MAAM,kBAAC,GAAD,EAAS,WAAU,SAAU,CAAA;MACnC,eAAe,EAAS,YAAY;KACtC;KACA;MACE,IAAI;MACJ,OAAO,EAAE,+BAA+B,oBAAoB;MAC5D,aAAa,EACX,mCACA,8CACF;MACA,MAAM,kBAAC,GAAD,EAAQ,WAAU,SAAU,CAAA;MAClC,eAAe,EAAS,kBAAkB;KAC5C;IACF;GACD,CAAA;GAED,kBAAC,GAAD,EAAmB,kBAAkB,KAAK,EAAQ,EAAI,CAAA;GAErD,EAAS,SAAS,KAAK,IACtB,kBAAC,OAAD;IAAK,WAAU;cACZ;KACC;MACE,QAAQ;MACR,OAAO,EAAE,uCAAuC,WAAW;MAC3D,MAAM;KACR;KACA;MACE,QAAQ;MACR,OAAO,EAAE,uCAAuC,YAAY;MAC5D,MAAM;KACR;KACA;MACE,QAAQ;MACR,OAAO,EAAE,uCAAuC,WAAW;MAC3D,MAAM;KACR;KACA;MACE,QAAQ;MACR,OAAO,EAAE,uCAAuC,WAAW;MAC3D,MAAM;KACR;IACF,CAAC,CAAC,KAAK,MAAU;KACf,IAAM,IAAO,EAAM,MACb,IAAW,MAAiB,EAAM,QAClC,IAAQ,GAAa,EAAM;KACjC,OACE,kBAAC,UAAD;MAEE,MAAK;MACL,gBAAc;MACd,eACE,EAAgB,IAAW,OAAQ,EAAM,MAA8B;MAEzE,WAAW,EACT,6MACA,IACI,qDACA,iEACN;gBAZF;OAcE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA;OACvB,CAAA;OACL,kBAAC,KAAD;QAAG,WAAU;kBAA2C,EAAM;OAAS,CAAA;OACvE,kBAAC,KAAD;QAAG,WAAU;kBAAwD;OAAS,CAAA;MACxE;QAlBD,EAAM,MAkBL;IAEZ,CAAC;GACE,CAAA,IACH;GAEH,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAE,+BAA+B,kCAAkC,CAAC,CAAC,QACpE,cACA,EAAa,CAAY,CAAC,CAAC,YAAY,CACzC;IACI,CAAA,GACN,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAgB,IAAI;KACnC,WAAU;eAET,EAAE,gCAAgC,mBAAmB;IAChD,CAAA,CACL;QACH;GAEH,EAAS,WAAW,IACnB,kBAAC,GAAD;IACE,cAAa;IACb,OACE,IACI,EAAE,uCAAuC,wBAAwB,CAAC,CAAC,QACjE,cACA,EAAa,CAAY,CAAC,CAAC,YAAY,CACzC,IACA,EAAE,+BAA+B,iBAAiB;IAExD,aACE,IACI,EACE,6CACA,6EACF,IACA,EACE,qCACA,gEACF;GAEP,CAAA,IAED,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAA5C,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAE,mCAAmC,eAAe;IACnD,CAAA,GACJ,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAS,KAAK,MAAY;MACzB,IAAM,IAAU,EAAW,EAAQ,OAAO,GACpC,IAAW,EAAW,EAAQ,QAAQ,GACtC,IAAY,EAAW,EAAQ,SAAS,GACxC,IAAY,IACd,IACE,GAAG,EAAQ,KAAK,MAChB,IACF;MACJ,OACE,kBAAC,IAAD;OAEE,OAAO,EAAQ;OACf,MAAM,EAAQ;OACd,OAAO,GAAG,EAAQ,SAAS,GAAG,OAAO,EAAQ,UAAU,CAAC,CAAC,QAAQ,CAAC;OAClE,OACE,kBAAC,IAAD;QAAa,QAAQ,EAAQ;QAAQ,MAAM,EAAQ,WAAW;OAAc,CAAA;OAE9E,QACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,QAAD,EAAA,UAAO,EAAa,EAAQ,WAAW,EAAQ,CAAA;UAC9C,IACC,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD;YAAe,WAAU;YAAW,eAAY;WAAQ,CAAA,GACvD,CACG;eACJ;UACJ,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD;YAAO,WAAU;YAAW,eAAY;WAAQ,CAAA,GAC/C,EAAQ,WAAW,IAChB,EAAE,6BAA6B,SAAS,IACxC,EAAE,2BAA2B,cAAc,CAAC,CAAC,QAC3C,SACA,OAAO,EAAQ,MAAM,CACvB,CACA;;UACN,kBAAC,QAAD,EAAA,UACG,EAAE,4BAA4B,qBAAqB,CAAC,CAAC,QACpD,cACA,EAAa,EAAQ,aAAa,CAAC,CAAC,YAAY,CAClD,EACI,CAAA;UACL,EAAQ,qBAAqB,QAC9B,EAAQ,qBAAqB,KAAA,IAC3B,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD;YAAM,WAAU;YAAW,eAAY;WAAQ,CAAA,GAC9C,EAAE,mCAAmC,sBAAsB,CAAC,CAAC,QAC5D,UACA,EAAQ,iBAAiB,eAAe,CAC1C,CACI;eACJ;SACD;YACJ,EAAQ,WAAW,cA0BhB,OAzBF,kBAAC,GAAD;SACE,OAAM;SACN,SAAS,CACP;UACE,OAAO,EAAE,uCAAuC,oBAAoB;UACpE,gBACE,EAAgB;WACd,IAAI,EAAQ;WACZ,eAAe,EAAQ;WACvB,QAAQ,EAAQ;UAClB,CAAC;UACH,MAAM,kBAAC,GAAD,CAAS,CAAA;SACjB,GACA;UACE,OAAO,EAAE,mCAAmC,gBAAgB;UAC5D,gBACE,EAAgB;WACd,IAAI,EAAQ;WACZ,eAAe,EAAQ;UACzB,CAAC;UACH,QAAQ;UACR,MAAM,kBAAC,GAAD,CAAU,CAAA;SAClB,CACF;QACD,CAAA,CAEA;;MAER,GAxEM,EAAQ,EAwEd;KAEL,CAAC;IACE,CAAA,CACI;;GAGb,kBAAC,IAAD;IACE,MAAM,MAAiB;IACvB,eAAe,MAAS;KACtB,AAAK,KAAM,EAAgB,IAAI;IACjC;cAEA,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACG,EAAE,uCAAuC,sBAAsB,EAChD,CAAA,GAClB,kBAAC,GAAD,EAAA,UACG,EACC,sCACA,yDACF,CAAC,CAAC,QAAQ,WAAW,GAAc,iBAAiB,EAAE,EAChC,CAAA,CACP,EAAA,CAAA,GACnB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,IAAD;KAAmB,UAAU;eAC1B,EAAE,sCAAsC,cAAc;IACtC,CAAA,GACnB,kBAAC,IAAD;KACE,WAAU;KACV,UAAU;KACV,UAAU,MAAU;MAElB,AADA,EAAM,eAAe,GACrB,GAAmB;KACrB;eAEC,IACG,EAAE,4CAA4C,aAAa,IAC3D,EAAE,yCAAyC,gBAAgB;IAC9C,CAAA,CACF,EAAA,CAAA,CACD,EAAA,CAAA;GACT,CAAA;GAEZ,IACC,kBAAC,GAAD;IACE,MAAM,MAAiB;IACvB,eAAe,MAAS;KACtB,AAAK,KAAM,EAAgB,IAAI;IACjC;IACA,WAAW,EAAa;IACxB,eAAe,EAAa;IAC5B,eAAe,EAAa;GAC7B,CAAA,IACC;EACK;;AAEf"}
|
|
1
|
+
{"version":3,"file":"BookingsPage.js","names":[],"sources":["../../../src/ggnomad/pages/BookingsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n CalendarCheck,\n CalendarRange,\n Compass,\n Hotel,\n Leaf,\n Plus,\n QrCode,\n Ticket,\n Users,\n XCircle,\n} from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n CTAOverflowMenu,\n GlassCard,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n CANCEL_GGNOMAD_BOOKING_MUTATION,\n CREATE_GGNOMAD_BOOKING_MUTATION,\n GGNOMAD_BOOKABLE_SAMPLE_QUERY,\n GGNOMAD_BOOKINGS_QUERY,\n} from '../graphql';\nimport { formatDate, humanizeEnum } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport { EntityCard, RedeemTicketPanel, StatusBadge, TicketDialog } from '../components';\nimport { notify } from '../utils/notify';\nimport { cn } from '../utils/cn';\nimport { stayDates } from '../utils/bookingDates';\nimport { useFocusedRecordParam } from '../hooks/useFocusedRecordParam';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\n/** Every value of `GgnomadBookingStatus`, the argument `ggnomadBookings` accepts. */\ntype BookingStatusFilter =\n 'PENDING' | 'CONFIRMED' | 'CHECKED_IN' | 'COMPLETED' | 'CANCELLED' | 'NO_SHOW' | 'REFUNDED';\n\ntype BookingRow = {\n id: string;\n referenceCode: string;\n bookingType: string;\n itemName: string;\n status: string;\n guestName: string;\n guests: number;\n checkIn?: string | null;\n checkOut?: string | null;\n eventDate?: string | null;\n totalPrice: number;\n currency: string;\n paymentStatus: string;\n carbonEstimateKg?: number | null;\n createdAt: string;\n};\n\ntype PricedListing = { id: string; name: string; price: number; currency: string };\n\ntype CancelTarget = { id: string; referenceCode: string };\n\ntype TicketTarget = { id: string; referenceCode: string; status: string };\n\nexport function BookingsPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const [cancelTarget, setCancelTarget] = useState<CancelTarget | null>(null);\n const [ticketTarget, setTicketTarget] = useState<TicketTarget | null>(null);\n const [statusFilter, setStatusFilter] = useState<BookingStatusFilter | null>(null);\n // The booking the user is focused on lives in `?id=`, so the assistant's page\n // context can resolve \"cancel this one\" (GGNOMAD_BOOKING) and a reload or a\n // shared link keeps the same booking focused.\n const [focusedBookingId, setFocusedBookingId] = useFocusedRecordParam();\n\n const { data, refetch } = useQuery(\n GGNOMAD_BOOKINGS_QUERY as TypedDocumentNode<\n { ggnomadBookings: BookingRow[] },\n { limit: number; offset: number; status?: BookingStatusFilter }\n >,\n { variables: { limit: 20, offset: 0, status: statusFilter ?? undefined } }\n );\n\n // One real listing of each type so the demo booking targets an id that\n // actually exists in the workspace (the backend rejects synthetic ids).\n const { data: sampleData } = useQuery(\n GGNOMAD_BOOKABLE_SAMPLE_QUERY as TypedDocumentNode<\n {\n ggnomadProperties: Array<{\n id: string;\n name: string;\n pricePerNight: number;\n currency: string;\n }>;\n ggnomadTours: Array<{ id: string; name: string; price: number; currency: string }>;\n ggnomadEvents: Array<{ id: string; name: string; price: number; currency: string }>;\n ggnomadActivities: Array<{ id: string; name: string; price: number; currency: string }>;\n },\n Record<string, never>\n >\n );\n\n const [createBooking, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { createGgnomadBooking: { id: string; referenceCode: string; itemName: string } },\n {\n input: {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n guestName: string;\n guestEmail: string;\n guests: number;\n totalPrice: number;\n currency: string;\n paymentMethod: string;\n checkIn?: string;\n checkOut?: string;\n };\n }\n >\n );\n\n const [cancelBooking, { loading: cancelling }] = useMutation(\n CANCEL_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { cancelGgnomadBooking: { id: string; status: string } },\n { id: string; reason: string }\n >\n );\n\n const bookings = useMemo(() => data?.ggnomadBookings ?? [], [data?.ggnomadBookings]);\n\n const statusCounts = useMemo(() => {\n const counts: Record<string, number> = {\n CONFIRMED: 0,\n CHECKED_IN: 0,\n COMPLETED: 0,\n CANCELLED: 0,\n };\n bookings.forEach((booking) => {\n const status = booking.status?.toUpperCase();\n if (status && status in counts) {\n counts[status] += 1;\n }\n });\n return counts;\n }, [bookings]);\n\n /** First available real listing, in priority order, mapped to a booking type. */\n const resolveDemoTarget = (): { type: BookingType; listing: PricedListing } | null => {\n const property = sampleData?.ggnomadProperties?.[0];\n if (property) {\n return {\n type: 'PROPERTY',\n listing: {\n id: property.id,\n name: property.name,\n price: property.pricePerNight,\n currency: property.currency,\n },\n };\n }\n const tour = sampleData?.ggnomadTours?.[0];\n if (tour) return { type: 'TOUR', listing: tour };\n const event = sampleData?.ggnomadEvents?.[0];\n if (event) return { type: 'EVENT', listing: event };\n const activity = sampleData?.ggnomadActivities?.[0];\n if (activity) return { type: 'ACTIVITY', listing: activity };\n return null;\n };\n\n const handleCreateDemoBooking = async () => {\n const target = resolveDemoTarget();\n if (!target) {\n notify.warning(\n t('ggnomad.bookings.toast.noListingsTitle', 'No listings available to book yet'),\n t(\n 'ggnomad.bookings.toast.noListingsBody',\n 'Add a property, tour, event or activity first, then create a booking against it.'\n )\n );\n return;\n }\n const { type, listing } = target;\n try {\n const result = await createBooking({\n variables: {\n input: {\n bookingType: type,\n itemId: listing.id,\n itemName: listing.name,\n guestName: t('ggnomad.bookings.demo.guestName', 'Demo Guest'),\n guestEmail: 'demo@burdenoff.com',\n guests: 2,\n totalPrice: Number(listing.price ?? 0),\n currency: listing.currency ?? 'USD',\n paymentMethod: 'card',\n // The service prices a stay per night (BOFF-10193), so the demo\n // stay is one night a week from today.\n ...(type === 'PROPERTY' ? stayDates(null, null) : {}),\n },\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.bookings.toast.createErrorTitle', 'Could not create booking'),\n result.error.message\n );\n return;\n }\n const created = result.data?.createGgnomadBooking;\n notify.success(\n t('ggnomad.bookings.toast.createSuccessTitle', 'Booking confirmed'),\n created\n ? t('ggnomad.bookings.toast.createSuccessBody', '{{ref}} · {{item}}')\n .replace('{{ref}}', created.referenceCode)\n .replace('{{item}}', created.itemName)\n : listing.name\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.bookings.toast.createErrorTitle', 'Could not create booking'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const confirmCancel = async () => {\n if (!cancelTarget) return;\n try {\n const result = await cancelBooking({\n variables: {\n id: cancelTarget.id,\n reason: t('ggnomad.bookings.actions.cancelReason', 'Cancelled via UI'),\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.bookings.toast.cancelErrorTitle', 'Could not cancel booking'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.bookings.toast.cancelSuccessTitle', 'Booking cancelled'),\n cancelTarget.referenceCode\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.bookings.toast.cancelErrorTitle', 'Could not cancel booking'),\n err instanceof Error ? err.message : String(err)\n );\n } finally {\n setCancelTarget(null);\n }\n };\n\n return (\n <PageShell\n title={t('ggnomad.bookings.title', 'Bookings')}\n titleAccent={t('ggnomad.bookings.title.accent', 'Bookings')}\n subtitle={t(\n 'ggnomad.bookings.subtitle',\n 'End-to-end booking operations with real API mutations'\n )}\n actions={\n <CTAOverflowMenu\n primary={{\n label: t('ggnomad.bookings.actions.createDemoBooking', 'Create demo booking'),\n onSelect: () => void handleCreateDemoBooking(),\n disabled: creating,\n icon: <Plus />,\n }}\n actions={[]}\n />\n }\n >\n <PagePurpose icon={<CalendarCheck className=\"size-4\" />}>\n {t(\n 'ggnomad.bookings.purpose',\n 'Your single place to manage every reservation across travel, stays, tours and events. Track each booking’s status and reference code in real time, and cancel a reservation when plans change — all backed by live API mutations.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"ggnomad-bookings\"\n steps={[\n {\n id: 'book-stay',\n label: t('ggnomad.bookings.next.stay', 'Book a place to stay'),\n description: t(\n 'ggnomad.bookings.next.stayDesc',\n 'Browse hospitality listings and reserve a property.'\n ),\n icon: <Hotel className=\"size-4\" />,\n onClick: () => navigate('../hospitality'),\n },\n {\n id: 'book-tour',\n label: t('ggnomad.bookings.next.tour', 'Add a tour or activity'),\n description: t(\n 'ggnomad.bookings.next.tourDesc',\n 'Pick a curated experience from Tourism.'\n ),\n icon: <Compass className=\"size-4\" />,\n onClick: () => navigate('../tourism'),\n },\n {\n id: 'book-event',\n label: t('ggnomad.bookings.next.event', 'Grab event tickets'),\n description: t(\n 'ggnomad.bookings.next.eventDesc',\n 'Find live events to round out the itinerary.'\n ),\n icon: <Ticket className=\"size-4\" />,\n onClick: () => navigate('../entertainment'),\n },\n ]}\n />\n\n <RedeemTicketPanel onRedeemed={() => void refetch()} />\n\n {bookings.length > 0 || statusFilter ? (\n <div className=\"grid gap-3 sm:grid-cols-4\">\n {[\n {\n status: 'CONFIRMED',\n label: t('ggnomad.bookings.pipeline.confirmed', 'Confirmed'),\n icon: CalendarCheck,\n },\n {\n status: 'CHECKED_IN',\n label: t('ggnomad.bookings.pipeline.checkedIn', 'Checked-in'),\n icon: Hotel,\n },\n {\n status: 'COMPLETED',\n label: t('ggnomad.bookings.pipeline.completed', 'Completed'),\n icon: Ticket,\n },\n {\n status: 'CANCELLED',\n label: t('ggnomad.bookings.pipeline.cancelled', 'Cancelled'),\n icon: XCircle,\n },\n ].map((stage) => {\n const Icon = stage.icon;\n const selected = statusFilter === stage.status;\n const count = statusCounts[stage.status];\n return (\n <button\n key={stage.status}\n type=\"button\"\n aria-pressed={selected}\n onClick={() =>\n setStatusFilter(selected ? null : (stage.status as BookingStatusFilter))\n }\n className={cn(\n 'flex flex-col items-center gap-2 rounded-lg border p-3 text-center transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg',\n selected\n ? 'border-action-primary-bg bg-action-primary-bg/10'\n : 'border-border-seam bg-bg-surface/40 hover:border-border-default'\n )}\n >\n <div className=\"flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"size-4\" />\n </div>\n <p className=\"text-xs font-medium text-text-secondary\">{stage.label}</p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">{count}</p>\n </button>\n );\n })}\n </div>\n ) : null}\n\n {statusFilter ? (\n <div className=\"flex flex-wrap items-center gap-3 text-sm\">\n <span className=\"text-text-secondary\">\n {t('ggnomad.bookings.filteredBy', 'Showing {{status}} bookings only').replace(\n '{{status}}',\n humanizeEnum(statusFilter).toLowerCase()\n )}\n </span>\n <button\n type=\"button\"\n onClick={() => setStatusFilter(null)}\n className=\"font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.bookings.clearFilter', 'Show all bookings')}\n </button>\n </div>\n ) : null}\n\n {bookings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n statusFilter\n ? t('ggnomad.bookings.noneForStatusTitle', 'No {{status}} bookings').replace(\n '{{status}}',\n humanizeEnum(statusFilter).toLowerCase()\n )\n : t('ggnomad.bookings.emptyTitle', 'No bookings yet')\n }\n description={\n statusFilter\n ? t(\n 'ggnomad.bookings.noneForStatusDescription',\n 'Nothing is in this stage right now — clear the filter to see every booking.'\n )\n : t(\n 'ggnomad.bookings.emptyDescription',\n 'Create your first booking to see it tracked here in real time.'\n )\n }\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.bookings.activeBookings', 'Your bookings')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {bookings.map((booking) => {\n const checkIn = formatDate(booking.checkIn);\n const checkOut = formatDate(booking.checkOut);\n const eventDate = formatDate(booking.eventDate);\n const dateLabel = checkIn\n ? checkOut\n ? `${checkIn} – ${checkOut}`\n : checkIn\n : eventDate;\n return (\n <EntityCard\n key={booking.id}\n selected={focusedBookingId === booking.id}\n onSelect={() =>\n setFocusedBookingId(focusedBookingId === booking.id ? null : booking.id)\n }\n title={booking.referenceCode}\n meta={booking.itemName}\n price={`${booking.currency} ${Number(booking.totalPrice).toFixed(2)}`}\n badge={\n <StatusBadge status={booking.status} live={booking.status !== 'CANCELLED'} />\n }\n footer={\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary\">\n <span>{humanizeEnum(booking.bookingType)}</span>\n {dateLabel ? (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarRange className=\"size-3.5\" aria-hidden=\"true\" />\n {dateLabel}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Users className=\"size-3.5\" aria-hidden=\"true\" />\n {booking.guests === 1\n ? t('ggnomad.bookings.oneGuest', '1 guest')\n : t('ggnomad.bookings.guests', '{{n}} guests').replace(\n '{{n}}',\n String(booking.guests)\n )}\n </span>\n <span>\n {t('ggnomad.bookings.payment', 'Payment: {{status}}').replace(\n '{{status}}',\n humanizeEnum(booking.paymentStatus).toLowerCase()\n )}\n </span>\n {booking.carbonEstimateKg !== null &&\n booking.carbonEstimateKg !== undefined ? (\n <span className=\"inline-flex items-center gap-1 tabular-nums text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.bookings.carbonEstimate', '≈{{kg}} kg CO₂e est.').replace(\n '{{kg}}',\n booking.carbonEstimateKg.toLocaleString()\n )}\n </span>\n ) : null}\n </div>\n {booking.status !== 'CANCELLED' ? (\n <CTAOverflowMenu\n align=\"end\"\n actions={[\n {\n label: t('ggnomad.bookings.actions.viewTicket', 'View mobile ticket'),\n onSelect: () => {\n setFocusedBookingId(booking.id);\n setTicketTarget({\n id: booking.id,\n referenceCode: booking.referenceCode,\n status: booking.status,\n });\n },\n icon: <QrCode />,\n },\n {\n label: t('ggnomad.bookings.actions.cancel', 'Cancel booking'),\n onSelect: () => {\n setFocusedBookingId(booking.id);\n setCancelTarget({\n id: booking.id,\n referenceCode: booking.referenceCode,\n });\n },\n intent: 'destructive',\n icon: <XCircle />,\n },\n ]}\n />\n ) : null}\n </div>\n }\n />\n );\n })}\n </div>\n </GlassCard>\n )}\n\n <AlertDialog\n open={cancelTarget !== null}\n onOpenChange={(open) => {\n if (!open) setCancelTarget(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {t('ggnomad.bookings.cancelDialog.title', 'Cancel this booking?')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t(\n 'ggnomad.bookings.cancelDialog.body',\n 'This releases reservation {{ref}} and cannot be undone.'\n ).replace('{{ref}}', cancelTarget?.referenceCode ?? '')}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={cancelling}>\n {t('ggnomad.bookings.cancelDialog.keep', 'Keep booking')}\n </AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover\"\n disabled={cancelling}\n onClick={(event) => {\n event.preventDefault();\n void confirmCancel();\n }}\n >\n {cancelling\n ? t('ggnomad.bookings.cancelDialog.cancelling', 'Cancelling…')\n : t('ggnomad.bookings.cancelDialog.confirm', 'Cancel booking')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n {ticketTarget ? (\n <TicketDialog\n open={ticketTarget !== null}\n onOpenChange={(open) => {\n if (!open) setTicketTarget(null);\n }}\n bookingId={ticketTarget.id}\n referenceCode={ticketTarget.referenceCode}\n bookingStatus={ticketTarget.status}\n />\n ) : null}\n </PageShell>\n );\n}\n"],"mappings":"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"}
|