@burdenoff/microfe-ggnomad 2026.1016.1 → 2026.1029.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.
Files changed (64) hide show
  1. package/dist/ggnomad/GGNomadRoot.js +4 -2
  2. package/dist/ggnomad/GGNomadRoot.js.map +1 -1
  3. package/dist/ggnomad/GGNomadRoutes.js.map +1 -1
  4. package/dist/ggnomad/components/AuditTrailDialog.js.map +1 -1
  5. package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -1
  6. package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
  7. package/dist/ggnomad/components/BookingPanel.js +53 -70
  8. package/dist/ggnomad/components/BookingPanel.js.map +1 -1
  9. package/dist/ggnomad/components/CreateProviderDialog.js.map +1 -1
  10. package/dist/ggnomad/components/CreateTravelPolicyDialog.js.map +1 -1
  11. package/dist/ggnomad/components/GroupTripPanel.js.map +1 -1
  12. package/dist/ggnomad/components/ListingEditDialog.js.map +1 -1
  13. package/dist/ggnomad/components/ListingFilters.js.map +1 -1
  14. package/dist/ggnomad/components/PhotoGallery.js.map +1 -1
  15. package/dist/ggnomad/components/ProviderCard.js.map +1 -1
  16. package/dist/ggnomad/components/ProviderDashboardCard.js.map +1 -1
  17. package/dist/ggnomad/components/RatingSummary.js.map +1 -1
  18. package/dist/ggnomad/components/RedeemTicketPanel.js.map +1 -1
  19. package/dist/ggnomad/components/ReviewsSection.js.map +1 -1
  20. package/dist/ggnomad/components/StatusBadge.js.map +1 -1
  21. package/dist/ggnomad/components/TicketDialog.js +1 -1
  22. package/dist/ggnomad/components/TicketDialog.js.map +1 -1
  23. package/dist/ggnomad/components/index.js +26 -25
  24. package/dist/ggnomad/index.js +3 -2
  25. package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
  26. package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
  27. package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
  28. package/dist/ggnomad/pages/BookingsPage.js +54 -52
  29. package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
  30. package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
  31. package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
  32. package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
  33. package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
  34. package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
  35. package/dist/ggnomad/pages/OperationsQueuePage.js.map +1 -1
  36. package/dist/ggnomad/pages/PageShell.js.map +1 -1
  37. package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
  38. package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
  39. package/dist/ggnomad/pages/SearchPage.js +1 -1
  40. package/dist/ggnomad/pages/SearchPage.js.map +1 -1
  41. package/dist/ggnomad/pages/StorefrontPage.js.map +1 -1
  42. package/dist/ggnomad/pages/StorefrontPublicPage.js.map +1 -1
  43. package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
  44. package/dist/ggnomad/pages/TourismPage.js.map +1 -1
  45. package/dist/ggnomad/pages/TravelPage.js +1 -1
  46. package/dist/ggnomad/pages/TravelPage.js.map +1 -1
  47. package/dist/ggnomad/pages/TravelPolicyPage.js.map +1 -1
  48. package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
  49. package/dist/ggnomad/pages/TripPlanDetailPage.js +278 -273
  50. package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
  51. package/dist/ggnomad/pages/TripsPage.js.map +1 -1
  52. package/dist/ggnomad/providers/GGNomadProvider.js.map +1 -1
  53. package/dist/ggnomad/utils/bookingDates.d.ts +21 -0
  54. package/dist/ggnomad/utils/bookingDates.d.ts.map +1 -0
  55. package/dist/ggnomad/utils/bookingDates.js +22 -0
  56. package/dist/ggnomad/utils/bookingDates.js.map +1 -0
  57. package/dist/ggnomad/utils/format.js.map +1 -1
  58. package/dist/ggnomad/utils/notify.js +1 -1
  59. package/dist/ggnomad/utils/providerListControls.js +1 -1
  60. package/dist/ggnomad/utils/providerListControls.js.map +1 -1
  61. package/dist/ggnomad/utils/providerTypeMeta.js.map +1 -1
  62. package/dist/utils/pageContext.js +1 -1
  63. package/dist/utils/pageContext.js.map +1 -1
  64. package/package.json +18 -17
@@ -2,26 +2,27 @@ 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 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";
5
+ import { StatusBadge as ee } from "../components/StatusBadge.js";
6
+ import { EntityCard as te } from "../components/EntityCard.js";
7
+ import { notify as c } from "../utils/notify.js";
8
+ import { TicketDialog as l } from "../components/TicketDialog.js";
9
+ import { RedeemTicketPanel as u } from "../components/RedeemTicketPanel.js";
10
10
  import "../components/index.js";
11
- import { useMemo as p, useState as m } from "react";
12
- import { CalendarCheck as h, CalendarRange as g, Compass as _, Hotel as v, Leaf as y, Plus as b, QrCode as x, Ticket as S, Users as C, XCircle as w } from "lucide-react";
13
- import { useI18n as T } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
- import { useNavigate as E } from "react-router";
15
- import { useMutation as D, useQuery as O } from "@apollo/client/react";
16
- import { AlertDialog as ee, AlertDialogAction as te, AlertDialogCancel as ne, AlertDialogContent as re, AlertDialogDescription as k, AlertDialogFooter as A, AlertDialogHeader as j, AlertDialogTitle as M, CTAOverflowMenu as N, GlassCard as P, IllustratedEmptyState as F, NextSteps as I, PagePurpose as L } from "@burdenoff/fe-libs/ui";
11
+ import { stayDates as d } from "../utils/bookingDates.js";
12
+ import { useMemo as f, useState as p } from "react";
13
+ import { CalendarCheck as m, CalendarRange as h, Compass as g, Hotel as _, Leaf as v, Plus as y, QrCode as b, Ticket as x, Users as S, XCircle as C } from "lucide-react";
14
+ import { useI18n as w } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
15
+ import { useNavigate as T } from "react-router";
16
+ import { useMutation as E, useQuery as D } from "@apollo/client/react";
17
+ import { AlertDialog as ne, AlertDialogAction as re, AlertDialogCancel as ie, AlertDialogContent as O, AlertDialogDescription as k, AlertDialogFooter as A, AlertDialogHeader as j, AlertDialogTitle as M, CTAOverflowMenu as N, GlassCard as P, IllustratedEmptyState as F, NextSteps as I, PagePurpose as L } from "@burdenoff/fe-libs/ui";
17
18
  import { jsx as R, jsxs as z } from "react/jsx-runtime";
18
19
  //#region src/ggnomad/pages/BookingsPage.tsx
19
20
  function B() {
20
- let { t: B } = T(), V = E(), [H, U] = m(null), [W, G] = m(null), [K, q] = m(null), { data: J, refetch: Y } = O(r, { variables: {
21
+ let { t: B } = w(), V = T(), [H, U] = p(null), [W, G] = p(null), [K, q] = p(null), { data: J, refetch: Y } = D(r, { variables: {
21
22
  limit: 20,
22
23
  offset: 0,
23
24
  status: K ?? void 0
24
- } }), { data: X } = O(n), [ie, { loading: ae }] = D(t), [oe, { loading: Z }] = D(e), Q = p(() => J?.ggnomadBookings ?? [], [J?.ggnomadBookings]), $ = p(() => {
25
+ } }), { data: X } = D(n), [ae, { loading: oe }] = E(t), [se, { loading: Z }] = E(e), Q = f(() => J?.ggnomadBookings ?? [], [J?.ggnomadBookings]), ce = f(() => {
25
26
  let e = {
26
27
  CONFIRMED: 0,
27
28
  CHECKED_IN: 0,
@@ -32,7 +33,7 @@ function B() {
32
33
  let n = t.status?.toUpperCase();
33
34
  n && n in e && (e[n] += 1);
34
35
  }), e;
35
- }, [Q]), se = () => {
36
+ }, [Q]), le = () => {
36
37
  let e = X?.ggnomadProperties?.[0];
37
38
  if (e) return {
38
39
  type: "PROPERTY",
@@ -58,15 +59,15 @@ function B() {
58
59
  type: "ACTIVITY",
59
60
  listing: r
60
61
  } : null;
61
- }, ce = async () => {
62
- let e = se();
62
+ }, $ = async () => {
63
+ let e = le();
63
64
  if (!e) {
64
- u.warning(B("ggnomad.bookings.toast.noListingsTitle", "No listings available to book yet"), B("ggnomad.bookings.toast.noListingsBody", "Add a property, tour, event or activity first, then create a booking against it."));
65
+ c.warning(B("ggnomad.bookings.toast.noListingsTitle", "No listings available to book yet"), B("ggnomad.bookings.toast.noListingsBody", "Add a property, tour, event or activity first, then create a booking against it."));
65
66
  return;
66
67
  }
67
68
  let { type: t, listing: n } = e;
68
69
  try {
69
- let e = await ie({ variables: { input: {
70
+ let e = await ae({ variables: { input: {
70
71
  bookingType: t,
71
72
  itemId: n.id,
72
73
  itemName: n.name,
@@ -75,30 +76,31 @@ function B() {
75
76
  guests: 2,
76
77
  totalPrice: Number(n.price ?? 0),
77
78
  currency: n.currency ?? "USD",
78
- paymentMethod: "card"
79
+ paymentMethod: "card",
80
+ ...t === "PROPERTY" ? d(null, null) : {}
79
81
  } } });
80
82
  if (e.error) {
81
- u.error(B("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
83
+ c.error(B("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
82
84
  return;
83
85
  }
84
86
  let r = e.data?.createGgnomadBooking;
85
- u.success(B("ggnomad.bookings.toast.createSuccessTitle", "Booking confirmed"), r ? B("ggnomad.bookings.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : n.name), await Y();
87
+ c.success(B("ggnomad.bookings.toast.createSuccessTitle", "Booking confirmed"), r ? B("ggnomad.bookings.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : n.name), await Y();
86
88
  } catch (e) {
87
- u.error(B("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
89
+ c.error(B("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
88
90
  }
89
- }, le = async () => {
91
+ }, ue = async () => {
90
92
  if (H) try {
91
- let e = await oe({ variables: {
93
+ let e = await se({ variables: {
92
94
  id: H.id,
93
95
  reason: B("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
94
96
  } });
95
97
  if (e.error) {
96
- u.error(B("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
98
+ c.error(B("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
97
99
  return;
98
100
  }
99
- u.success(B("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), H.referenceCode), await Y();
101
+ c.success(B("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), H.referenceCode), await Y();
100
102
  } catch (e) {
101
- u.error(B("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
103
+ c.error(B("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
102
104
  } finally {
103
105
  U(null);
104
106
  }
@@ -110,15 +112,15 @@ function B() {
110
112
  actions: /* @__PURE__ */ R(N, {
111
113
  primary: {
112
114
  label: B("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
113
- onSelect: () => void ce(),
114
- disabled: ae,
115
- icon: /* @__PURE__ */ R(b, {})
115
+ onSelect: () => void $(),
116
+ disabled: oe,
117
+ icon: /* @__PURE__ */ R(y, {})
116
118
  },
117
119
  actions: []
118
120
  }),
119
121
  children: [
120
122
  /* @__PURE__ */ R(L, {
121
- icon: /* @__PURE__ */ R(h, { className: "size-4" }),
123
+ icon: /* @__PURE__ */ R(m, { className: "size-4" }),
122
124
  children: B("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.")
123
125
  }),
124
126
  /* @__PURE__ */ R(I, {
@@ -128,51 +130,51 @@ function B() {
128
130
  id: "book-stay",
129
131
  label: B("ggnomad.bookings.next.stay", "Book a place to stay"),
130
132
  description: B("ggnomad.bookings.next.stayDesc", "Browse hospitality listings and reserve a property."),
131
- icon: /* @__PURE__ */ R(v, { className: "size-4" }),
133
+ icon: /* @__PURE__ */ R(_, { className: "size-4" }),
132
134
  onClick: () => V("../hospitality")
133
135
  },
134
136
  {
135
137
  id: "book-tour",
136
138
  label: B("ggnomad.bookings.next.tour", "Add a tour or activity"),
137
139
  description: B("ggnomad.bookings.next.tourDesc", "Pick a curated experience from Tourism."),
138
- icon: /* @__PURE__ */ R(_, { className: "size-4" }),
140
+ icon: /* @__PURE__ */ R(g, { className: "size-4" }),
139
141
  onClick: () => V("../tourism")
140
142
  },
141
143
  {
142
144
  id: "book-event",
143
145
  label: B("ggnomad.bookings.next.event", "Grab event tickets"),
144
146
  description: B("ggnomad.bookings.next.eventDesc", "Find live events to round out the itinerary."),
145
- icon: /* @__PURE__ */ R(S, { className: "size-4" }),
147
+ icon: /* @__PURE__ */ R(x, { className: "size-4" }),
146
148
  onClick: () => V("../entertainment")
147
149
  }
148
150
  ]
149
151
  }),
150
- /* @__PURE__ */ R(f, { onRedeemed: () => void Y() }),
152
+ /* @__PURE__ */ R(u, { onRedeemed: () => void Y() }),
151
153
  Q.length > 0 || K ? /* @__PURE__ */ R("div", {
152
154
  className: "grid gap-3 sm:grid-cols-4",
153
155
  children: [
154
156
  {
155
157
  status: "CONFIRMED",
156
158
  label: B("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
157
- icon: h
159
+ icon: m
158
160
  },
159
161
  {
160
162
  status: "CHECKED_IN",
161
163
  label: B("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
162
- icon: v
164
+ icon: _
163
165
  },
164
166
  {
165
167
  status: "COMPLETED",
166
168
  label: B("ggnomad.bookings.pipeline.completed", "Completed"),
167
- icon: S
169
+ icon: x
168
170
  },
169
171
  {
170
172
  status: "CANCELLED",
171
173
  label: B("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
172
- icon: w
174
+ icon: C
173
175
  }
174
176
  ].map((e) => {
175
- let t = e.icon, n = K === e.status, r = $[e.status];
177
+ let t = e.icon, n = K === e.status, r = ce[e.status];
176
178
  return /* @__PURE__ */ z("button", {
177
179
  type: "button",
178
180
  "aria-pressed": n,
@@ -222,11 +224,11 @@ function B() {
222
224
  className: "grid gap-3 sm:grid-cols-2",
223
225
  children: Q.map((e) => {
224
226
  let t = i(e.checkIn), n = i(e.checkOut), r = i(e.eventDate), o = t ? n ? `${t} – ${n}` : t : r;
225
- return /* @__PURE__ */ R(l, {
227
+ return /* @__PURE__ */ R(te, {
226
228
  title: e.referenceCode,
227
229
  meta: e.itemName,
228
230
  price: `${e.currency} ${Number(e.totalPrice).toFixed(2)}`,
229
- badge: /* @__PURE__ */ R(c, {
231
+ badge: /* @__PURE__ */ R(ee, {
230
232
  status: e.status,
231
233
  live: e.status !== "CANCELLED"
232
234
  }),
@@ -238,14 +240,14 @@ function B() {
238
240
  /* @__PURE__ */ R("span", { children: a(e.bookingType) }),
239
241
  o ? /* @__PURE__ */ z("span", {
240
242
  className: "inline-flex items-center gap-1",
241
- children: [/* @__PURE__ */ R(g, {
243
+ children: [/* @__PURE__ */ R(h, {
242
244
  className: "size-3.5",
243
245
  "aria-hidden": "true"
244
246
  }), o]
245
247
  }) : null,
246
248
  /* @__PURE__ */ z("span", {
247
249
  className: "inline-flex items-center gap-1 tabular-nums",
248
- children: [/* @__PURE__ */ R(C, {
250
+ children: [/* @__PURE__ */ R(S, {
249
251
  className: "size-3.5",
250
252
  "aria-hidden": "true"
251
253
  }), e.guests === 1 ? B("ggnomad.bookings.oneGuest", "1 guest") : B("ggnomad.bookings.guests", "{{n}} guests").replace("{{n}}", String(e.guests))]
@@ -253,7 +255,7 @@ function B() {
253
255
  /* @__PURE__ */ R("span", { children: B("ggnomad.bookings.payment", "Payment: {{status}}").replace("{{status}}", a(e.paymentStatus).toLowerCase()) }),
254
256
  e.carbonEstimateKg !== null && e.carbonEstimateKg !== void 0 ? /* @__PURE__ */ z("span", {
255
257
  className: "inline-flex items-center gap-1 tabular-nums text-status-success-text",
256
- children: [/* @__PURE__ */ R(y, {
258
+ children: [/* @__PURE__ */ R(v, {
257
259
  className: "size-3.5",
258
260
  "aria-hidden": "true"
259
261
  }), B("ggnomad.bookings.carbonEstimate", "≈{{kg}} kg CO₂e est.").replace("{{kg}}", e.carbonEstimateKg.toLocaleString())]
@@ -268,7 +270,7 @@ function B() {
268
270
  referenceCode: e.referenceCode,
269
271
  status: e.status
270
272
  }),
271
- icon: /* @__PURE__ */ R(x, {})
273
+ icon: /* @__PURE__ */ R(b, {})
272
274
  }, {
273
275
  label: B("ggnomad.bookings.actions.cancel", "Cancel booking"),
274
276
  onSelect: () => U({
@@ -276,7 +278,7 @@ function B() {
276
278
  referenceCode: e.referenceCode
277
279
  }),
278
280
  intent: "destructive",
279
- icon: /* @__PURE__ */ R(w, {})
281
+ icon: /* @__PURE__ */ R(C, {})
280
282
  }]
281
283
  })]
282
284
  })
@@ -284,24 +286,24 @@ function B() {
284
286
  })
285
287
  })]
286
288
  }),
287
- /* @__PURE__ */ R(ee, {
289
+ /* @__PURE__ */ R(ne, {
288
290
  open: H !== null,
289
291
  onOpenChange: (e) => {
290
292
  e || U(null);
291
293
  },
292
- children: /* @__PURE__ */ z(re, { children: [/* @__PURE__ */ z(j, { children: [/* @__PURE__ */ R(M, { children: B("ggnomad.bookings.cancelDialog.title", "Cancel this booking?") }), /* @__PURE__ */ R(k, { children: B("ggnomad.bookings.cancelDialog.body", "This releases reservation {{ref}} and cannot be undone.").replace("{{ref}}", H?.referenceCode ?? "") })] }), /* @__PURE__ */ z(A, { children: [/* @__PURE__ */ R(ne, {
294
+ children: /* @__PURE__ */ z(O, { children: [/* @__PURE__ */ z(j, { children: [/* @__PURE__ */ R(M, { children: B("ggnomad.bookings.cancelDialog.title", "Cancel this booking?") }), /* @__PURE__ */ R(k, { children: B("ggnomad.bookings.cancelDialog.body", "This releases reservation {{ref}} and cannot be undone.").replace("{{ref}}", H?.referenceCode ?? "") })] }), /* @__PURE__ */ z(A, { children: [/* @__PURE__ */ R(ie, {
293
295
  disabled: Z,
294
296
  children: B("ggnomad.bookings.cancelDialog.keep", "Keep booking")
295
- }), /* @__PURE__ */ R(te, {
297
+ }), /* @__PURE__ */ R(re, {
296
298
  className: "bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover",
297
299
  disabled: Z,
298
300
  onClick: (e) => {
299
- e.preventDefault(), le();
301
+ e.preventDefault(), ue();
300
302
  },
301
303
  children: Z ? B("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : B("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
302
304
  })] })] })
303
305
  }),
304
- W ? /* @__PURE__ */ R(d, {
306
+ W ? /* @__PURE__ */ R(l, {
305
307
  open: W !== null,
306
308
  onOpenChange: (e) => {
307
309
  e || G(null);
@@ -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';\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 };\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 },\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":"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"}
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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/ggnomad/pages/DashboardPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowRight,\n Building,\n CalendarCheck,\n CalendarRange,\n Compass,\n DollarSign,\n Hotel,\n Search,\n Users,\n} from 'lucide-react';\nimport { Link, useNavigate } from 'react-router';\nimport { IllustratedEmptyState, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport {\n GGNOMAD_OVERVIEW_QUERY,\n GGNOMAD_BOOKINGS_QUERY,\n GGNOMAD_PROPERTIES_QUERY,\n GGNOMAD_TOURS_QUERY,\n GGNOMAD_EVENTS_QUERY,\n} from '../graphql';\nimport { formatDate } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatCard, StatusBadge } from '../components';\n\ntype RecentListing = { id: string; name: string; currency: string; images?: string[] };\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\n\nconst humanizeCategory = (key: string): string =>\n key\n .replace(/[_-]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase()) || key;\n\nexport function DashboardPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const overview = useQuery(\n GGNOMAD_OVERVIEW_QUERY as TypedDocumentNode<\n {\n ggnomadOverviewStats: {\n users: number;\n listings: number;\n bookings: number;\n monthlyRevenue: number;\n };\n ggnomadAnalytics: {\n totalRevenue: number;\n totalBookings: number;\n totalUsers: number;\n conversionRate: number;\n revenueByCategory: Record<string, unknown>;\n };\n },\n Record<string, never>\n >\n );\n const bookings = useQuery(\n GGNOMAD_BOOKINGS_QUERY as TypedDocumentNode<\n {\n ggnomadBookings: Array<{\n id: string;\n referenceCode: string;\n itemName: string;\n bookingType: string;\n status: string;\n guests: number;\n checkIn?: string | null;\n checkOut?: string | null;\n eventDate?: string | null;\n totalPrice: number;\n currency: string;\n }>;\n },\n { limit: number; offset: number }\n >,\n {\n variables: { limit: 5, offset: 0 },\n }\n );\n const properties = useQuery(\n GGNOMAD_PROPERTIES_QUERY as TypedDocumentNode<\n { ggnomadProperties: Array<RecentListing & { pricePerNight: number }> },\n { limit: number; offset: number }\n >,\n { variables: { limit: 4, offset: 0 } }\n );\n const tours = useQuery(\n GGNOMAD_TOURS_QUERY as TypedDocumentNode<\n { ggnomadTours: Array<RecentListing & { price: number }> },\n { limit: number; offset: number }\n >,\n {\n variables: { limit: 4, offset: 0 },\n }\n );\n const events = useQuery(\n GGNOMAD_EVENTS_QUERY as TypedDocumentNode<\n { ggnomadEvents: Array<RecentListing & { price: number; startsAt: string }> },\n { limit: number; offset: number }\n >,\n {\n variables: { limit: 4, offset: 0 },\n }\n );\n\n const stats = overview.data?.ggnomadOverviewStats;\n const analytics = overview.data?.ggnomadAnalytics;\n const recentBookings = bookings.data?.ggnomadBookings ?? [];\n\n const categories = useMemo(() => {\n const raw = analytics?.revenueByCategory ?? {};\n return Object.entries(raw)\n .map(([key, value]) => ({ label: humanizeCategory(key), value: Number(value) }))\n .filter((entry) => Number.isFinite(entry.value) && entry.value > 0)\n .sort((a, b) => b.value - a.value);\n }, [analytics?.revenueByCategory]);\n\n const maxCategory = categories.length > 0 ? categories[0].value : 0;\n\n return (\n <PageShell\n hero\n title={t('ggnomad.dashboard.title', 'GGNomad Dashboard')}\n titleAccent={t('ggnomad.dashboard.title.accent', 'GGNomad')}\n subtitle={t(\n 'ggnomad.dashboard.subtitle',\n 'Unified travel, hospitality, tourism and entertainment operations'\n )}\n >\n <PagePurpose icon={<Compass className=\"size-4\" />}>\n {t(\n 'ggnomad.dashboard.purpose',\n 'Your command centre for the whole GGNomad marketplace — see live revenue, users, listings and bookings across travel, hospitality, tourism and entertainment in one place, and jump straight into the area you want to work on.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"ggnomad-dashboard\"\n steps={[\n {\n id: 'search',\n label: t('ggnomad.dashboard.next.search', 'Search the catalogue'),\n description: t(\n 'ggnomad.dashboard.next.searchDesc',\n 'Find inventory across every domain at once.'\n ),\n icon: <Search className=\"size-4\" />,\n onClick: () => navigate('search'),\n },\n {\n id: 'stays',\n label: t('ggnomad.dashboard.next.stays', 'Browse places to stay'),\n description: t(\n 'ggnomad.dashboard.next.staysDesc',\n 'Explore hospitality listings and open a property.'\n ),\n icon: <Hotel className=\"size-4\" />,\n onClick: () => navigate('hospitality'),\n },\n {\n id: 'tourism',\n label: t('ggnomad.dashboard.next.tourism', 'Discover tours & activities'),\n description: t(\n 'ggnomad.dashboard.next.tourismDesc',\n 'Plan what to do at each destination.'\n ),\n icon: <Compass className=\"size-4\" />,\n onClick: () => navigate('tourism'),\n },\n {\n id: 'bookings',\n label: t('ggnomad.dashboard.next.bookings', 'Manage bookings'),\n description: t(\n 'ggnomad.dashboard.next.bookingsDesc',\n 'Track and cancel reservations in real time.'\n ),\n icon: <CalendarCheck className=\"size-4\" />,\n onClick: () => navigate('bookings'),\n },\n ]}\n />\n\n <section className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n hero\n label={t('ggnomad.dashboard.stats.monthlyRevenue', 'Monthly Revenue')}\n value={`$${Number(stats?.monthlyRevenue ?? 0).toLocaleString()}`}\n icon={<DollarSign />}\n />\n <StatCard\n label={t('ggnomad.dashboard.stats.users', 'Users')}\n value={Number(stats?.users ?? 0).toLocaleString()}\n icon={<Users />}\n />\n <StatCard\n label={t('ggnomad.dashboard.stats.listings', 'Listings')}\n value={Number(stats?.listings ?? 0).toLocaleString()}\n icon={<Building />}\n />\n <StatCard\n label={t('ggnomad.dashboard.stats.bookings', 'Bookings')}\n value={Number(stats?.bookings ?? 0).toLocaleString()}\n icon={<CalendarCheck />}\n />\n </section>\n\n <SectionCard title={t('ggnomad.dashboard.inventory.title', 'Latest inventory')}>\n <div className=\"grid gap-6 sm:grid-cols-3\">\n <div>\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.inventory.properties', 'Newest properties')}\n </p>\n <Link\n to=\"hospitality\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAll', 'View all')}\n <ArrowRight className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n <ul className=\"space-y-1 text-sm\">\n {(properties.data?.ggnomadProperties ?? []).map((property) => (\n <li key={property.id}>\n <Link\n to={`hospitality/${property.id}`}\n className=\"flex items-baseline justify-between gap-2 rounded px-1 py-0.5 hover:bg-bg-sunken\"\n >\n <span className=\"truncate text-text-primary\">{property.name}</span>\n <span className=\"flex-shrink-0 tabular-nums text-text-secondary\">\n {property.currency} {Number(property.pricePerNight).toFixed(0)}\n </span>\n </Link>\n </li>\n ))}\n {(properties.data?.ggnomadProperties?.length ?? 0) === 0 ? (\n <li className=\"text-text-secondary\">\n {t('ggnomad.dashboard.inventory.none', 'Nothing published yet')}\n </li>\n ) : null}\n </ul>\n </div>\n <div>\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.inventory.tours', 'Newest tours')}\n </p>\n <Link\n to=\"tourism\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAll', 'View all')}\n <ArrowRight className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n <ul className=\"space-y-1 text-sm\">\n {(tours.data?.ggnomadTours ?? []).map((tour) => (\n <li key={tour.id}>\n <Link\n to={`tourism/tours/${tour.id}`}\n className=\"flex items-baseline justify-between gap-2 rounded px-1 py-0.5 hover:bg-bg-sunken\"\n >\n <span className=\"truncate text-text-primary\">{tour.name}</span>\n <span className=\"flex-shrink-0 tabular-nums text-text-secondary\">\n {tour.currency} {Number(tour.price).toFixed(0)}\n </span>\n </Link>\n </li>\n ))}\n {(tours.data?.ggnomadTours?.length ?? 0) === 0 ? (\n <li className=\"text-text-secondary\">\n {t('ggnomad.dashboard.inventory.none', 'Nothing published yet')}\n </li>\n ) : null}\n </ul>\n </div>\n <div>\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.inventory.events', 'Upcoming events')}\n </p>\n <Link\n to=\"entertainment\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAll', 'View all')}\n <ArrowRight className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n <ul className=\"space-y-1 text-sm\">\n {(events.data?.ggnomadEvents ?? []).map((event) => (\n <li key={event.id}>\n <Link\n to={`entertainment/${event.id}`}\n className=\"flex items-baseline justify-between gap-2 rounded px-1 py-0.5 hover:bg-bg-sunken\"\n >\n <span className=\"truncate text-text-primary\">{event.name}</span>\n <span className=\"flex-shrink-0 tabular-nums text-text-secondary\">\n {formatDate(event.startsAt) ?? ''}\n </span>\n </Link>\n </li>\n ))}\n {(events.data?.ggnomadEvents?.length ?? 0) === 0 ? (\n <li className=\"text-text-secondary\">\n {t('ggnomad.dashboard.inventory.none', 'Nothing published yet')}\n </li>\n ) : null}\n </ul>\n </div>\n </div>\n </SectionCard>\n\n <SectionCard\n title={t('ggnomad.dashboard.sections.recentBookings', 'Recent bookings')}\n action={\n <Link\n to=\"bookings\"\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAllBookings', 'View all bookings')}\n <ArrowRight className=\"size-3.5\" aria-hidden=\"true\" />\n </Link>\n }\n >\n {recentBookings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.dashboard.recentBookings.emptyTitle', 'No recent bookings')}\n description={t(\n 'ggnomad.dashboard.recentBookings.emptyDescription',\n 'New bookings across travel, hospitality and tourism will appear here.'\n )}\n />\n ) : (\n <ul className=\"space-y-2 text-sm\">\n {recentBookings.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 <li\n key={booking.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-text-primary\">\n {booking.referenceCode} — {booking.itemName}\n </p>\n <p className=\"mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n {dateLabel ? (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarRange className=\"size-3\" 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\" aria-hidden=\"true\" />\n {booking.guests}\n </span>\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-3\">\n <span className=\"tabular-nums text-text-primary\">\n {booking.currency} {Number(booking.totalPrice).toFixed(2)}\n </span>\n <StatusBadge status={booking.status} />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard title={t('ggnomad.dashboard.sections.revenueByCategory', 'Revenue by category')}>\n {categories.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <span className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary\">\n <DollarSign className=\"size-5\" />\n </span>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.empty.title', 'Revenue data coming soon')}\n </p>\n <p className=\"max-w-sm text-sm text-text-secondary\">\n {t(\n 'ggnomad.dashboard.empty.description',\n 'Revenue by category appears here as bookings come in across stays, tours and events.'\n )}\n </p>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {categories.map((category, index) => {\n const pct = maxCategory > 0 ? Math.max((category.value / maxCategory) * 100, 2) : 0;\n return (\n <li key={category.label}>\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {category.label}\n </span>\n <span className=\"flex-shrink-0 text-sm tabular-nums text-text-secondary\">\n ${category.value.toLocaleString()}\n </span>\n </div>\n <div className=\"mt-1.5 h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full transition-[width] duration-500 ease-out\"\n style={{\n width: `${pct}%`,\n backgroundColor: CHART_COLORS[index % CHART_COLORS.length],\n }}\n />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n </PageShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/ggnomad/pages/DashboardPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowRight,\n Building,\n CalendarCheck,\n CalendarRange,\n Compass,\n DollarSign,\n Hotel,\n Search,\n Users,\n} from 'lucide-react';\nimport { Link, useNavigate } from 'react-router';\nimport { IllustratedEmptyState, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport {\n GGNOMAD_OVERVIEW_QUERY,\n GGNOMAD_BOOKINGS_QUERY,\n GGNOMAD_PROPERTIES_QUERY,\n GGNOMAD_TOURS_QUERY,\n GGNOMAD_EVENTS_QUERY,\n} from '../graphql';\nimport { formatDate } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatCard, StatusBadge } from '../components';\n\ntype RecentListing = { id: string; name: string; currency: string; images?: string[] };\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\n\nconst humanizeCategory = (key: string): string =>\n key\n .replace(/[_-]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase()) || key;\n\nexport function DashboardPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const overview = useQuery(\n GGNOMAD_OVERVIEW_QUERY as TypedDocumentNode<\n {\n ggnomadOverviewStats: {\n users: number;\n listings: number;\n bookings: number;\n monthlyRevenue: number;\n };\n ggnomadAnalytics: {\n totalRevenue: number;\n totalBookings: number;\n totalUsers: number;\n conversionRate: number;\n revenueByCategory: Record<string, unknown>;\n };\n },\n Record<string, never>\n >\n );\n const bookings = useQuery(\n GGNOMAD_BOOKINGS_QUERY as TypedDocumentNode<\n {\n ggnomadBookings: Array<{\n id: string;\n referenceCode: string;\n itemName: string;\n bookingType: string;\n status: string;\n guests: number;\n checkIn?: string | null;\n checkOut?: string | null;\n eventDate?: string | null;\n totalPrice: number;\n currency: string;\n }>;\n },\n { limit: number; offset: number }\n >,\n {\n variables: { limit: 5, offset: 0 },\n }\n );\n const properties = useQuery(\n GGNOMAD_PROPERTIES_QUERY as TypedDocumentNode<\n { ggnomadProperties: Array<RecentListing & { pricePerNight: number }> },\n { limit: number; offset: number }\n >,\n { variables: { limit: 4, offset: 0 } }\n );\n const tours = useQuery(\n GGNOMAD_TOURS_QUERY as TypedDocumentNode<\n { ggnomadTours: Array<RecentListing & { price: number }> },\n { limit: number; offset: number }\n >,\n {\n variables: { limit: 4, offset: 0 },\n }\n );\n const events = useQuery(\n GGNOMAD_EVENTS_QUERY as TypedDocumentNode<\n { ggnomadEvents: Array<RecentListing & { price: number; startsAt: string }> },\n { limit: number; offset: number }\n >,\n {\n variables: { limit: 4, offset: 0 },\n }\n );\n\n const stats = overview.data?.ggnomadOverviewStats;\n const analytics = overview.data?.ggnomadAnalytics;\n const recentBookings = bookings.data?.ggnomadBookings ?? [];\n\n const categories = useMemo(() => {\n const raw = analytics?.revenueByCategory ?? {};\n return Object.entries(raw)\n .map(([key, value]) => ({ label: humanizeCategory(key), value: Number(value) }))\n .filter((entry) => Number.isFinite(entry.value) && entry.value > 0)\n .sort((a, b) => b.value - a.value);\n }, [analytics?.revenueByCategory]);\n\n const maxCategory = categories.length > 0 ? categories[0].value : 0;\n\n return (\n <PageShell\n hero\n title={t('ggnomad.dashboard.title', 'GGNomad Dashboard')}\n titleAccent={t('ggnomad.dashboard.title.accent', 'GGNomad')}\n subtitle={t(\n 'ggnomad.dashboard.subtitle',\n 'Unified travel, hospitality, tourism and entertainment operations'\n )}\n >\n <PagePurpose icon={<Compass className=\"size-4\" />}>\n {t(\n 'ggnomad.dashboard.purpose',\n 'Your command centre for the whole GGNomad marketplace — see live revenue, users, listings and bookings across travel, hospitality, tourism and entertainment in one place, and jump straight into the area you want to work on.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"ggnomad-dashboard\"\n steps={[\n {\n id: 'search',\n label: t('ggnomad.dashboard.next.search', 'Search the catalogue'),\n description: t(\n 'ggnomad.dashboard.next.searchDesc',\n 'Find inventory across every domain at once.'\n ),\n icon: <Search className=\"size-4\" />,\n onClick: () => navigate('search'),\n },\n {\n id: 'stays',\n label: t('ggnomad.dashboard.next.stays', 'Browse places to stay'),\n description: t(\n 'ggnomad.dashboard.next.staysDesc',\n 'Explore hospitality listings and open a property.'\n ),\n icon: <Hotel className=\"size-4\" />,\n onClick: () => navigate('hospitality'),\n },\n {\n id: 'tourism',\n label: t('ggnomad.dashboard.next.tourism', 'Discover tours & activities'),\n description: t(\n 'ggnomad.dashboard.next.tourismDesc',\n 'Plan what to do at each destination.'\n ),\n icon: <Compass className=\"size-4\" />,\n onClick: () => navigate('tourism'),\n },\n {\n id: 'bookings',\n label: t('ggnomad.dashboard.next.bookings', 'Manage bookings'),\n description: t(\n 'ggnomad.dashboard.next.bookingsDesc',\n 'Track and cancel reservations in real time.'\n ),\n icon: <CalendarCheck className=\"size-4\" />,\n onClick: () => navigate('bookings'),\n },\n ]}\n />\n\n <section className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n hero\n label={t('ggnomad.dashboard.stats.monthlyRevenue', 'Monthly Revenue')}\n value={`$${Number(stats?.monthlyRevenue ?? 0).toLocaleString()}`}\n icon={<DollarSign />}\n />\n <StatCard\n label={t('ggnomad.dashboard.stats.users', 'Users')}\n value={Number(stats?.users ?? 0).toLocaleString()}\n icon={<Users />}\n />\n <StatCard\n label={t('ggnomad.dashboard.stats.listings', 'Listings')}\n value={Number(stats?.listings ?? 0).toLocaleString()}\n icon={<Building />}\n />\n <StatCard\n label={t('ggnomad.dashboard.stats.bookings', 'Bookings')}\n value={Number(stats?.bookings ?? 0).toLocaleString()}\n icon={<CalendarCheck />}\n />\n </section>\n\n <SectionCard title={t('ggnomad.dashboard.inventory.title', 'Latest inventory')}>\n <div className=\"grid gap-6 sm:grid-cols-3\">\n <div>\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.inventory.properties', 'Newest properties')}\n </p>\n <Link\n to=\"hospitality\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAll', 'View all')}\n <ArrowRight className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n <ul className=\"space-y-1 text-sm\">\n {(properties.data?.ggnomadProperties ?? []).map((property) => (\n <li key={property.id}>\n <Link\n to={`hospitality/${property.id}`}\n className=\"flex items-baseline justify-between gap-2 rounded px-1 py-0.5 hover:bg-bg-sunken\"\n >\n <span className=\"truncate text-text-primary\">{property.name}</span>\n <span className=\"flex-shrink-0 tabular-nums text-text-secondary\">\n {property.currency} {Number(property.pricePerNight).toFixed(0)}\n </span>\n </Link>\n </li>\n ))}\n {(properties.data?.ggnomadProperties?.length ?? 0) === 0 ? (\n <li className=\"text-text-secondary\">\n {t('ggnomad.dashboard.inventory.none', 'Nothing published yet')}\n </li>\n ) : null}\n </ul>\n </div>\n <div>\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.inventory.tours', 'Newest tours')}\n </p>\n <Link\n to=\"tourism\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAll', 'View all')}\n <ArrowRight className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n <ul className=\"space-y-1 text-sm\">\n {(tours.data?.ggnomadTours ?? []).map((tour) => (\n <li key={tour.id}>\n <Link\n to={`tourism/tours/${tour.id}`}\n className=\"flex items-baseline justify-between gap-2 rounded px-1 py-0.5 hover:bg-bg-sunken\"\n >\n <span className=\"truncate text-text-primary\">{tour.name}</span>\n <span className=\"flex-shrink-0 tabular-nums text-text-secondary\">\n {tour.currency} {Number(tour.price).toFixed(0)}\n </span>\n </Link>\n </li>\n ))}\n {(tours.data?.ggnomadTours?.length ?? 0) === 0 ? (\n <li className=\"text-text-secondary\">\n {t('ggnomad.dashboard.inventory.none', 'Nothing published yet')}\n </li>\n ) : null}\n </ul>\n </div>\n <div>\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.inventory.events', 'Upcoming events')}\n </p>\n <Link\n to=\"entertainment\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAll', 'View all')}\n <ArrowRight className=\"size-3\" aria-hidden=\"true\" />\n </Link>\n </div>\n <ul className=\"space-y-1 text-sm\">\n {(events.data?.ggnomadEvents ?? []).map((event) => (\n <li key={event.id}>\n <Link\n to={`entertainment/${event.id}`}\n className=\"flex items-baseline justify-between gap-2 rounded px-1 py-0.5 hover:bg-bg-sunken\"\n >\n <span className=\"truncate text-text-primary\">{event.name}</span>\n <span className=\"flex-shrink-0 tabular-nums text-text-secondary\">\n {formatDate(event.startsAt) ?? ''}\n </span>\n </Link>\n </li>\n ))}\n {(events.data?.ggnomadEvents?.length ?? 0) === 0 ? (\n <li className=\"text-text-secondary\">\n {t('ggnomad.dashboard.inventory.none', 'Nothing published yet')}\n </li>\n ) : null}\n </ul>\n </div>\n </div>\n </SectionCard>\n\n <SectionCard\n title={t('ggnomad.dashboard.sections.recentBookings', 'Recent bookings')}\n action={\n <Link\n to=\"bookings\"\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {t('ggnomad.dashboard.viewAllBookings', 'View all bookings')}\n <ArrowRight className=\"size-3.5\" aria-hidden=\"true\" />\n </Link>\n }\n >\n {recentBookings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.dashboard.recentBookings.emptyTitle', 'No recent bookings')}\n description={t(\n 'ggnomad.dashboard.recentBookings.emptyDescription',\n 'New bookings across travel, hospitality and tourism will appear here.'\n )}\n />\n ) : (\n <ul className=\"space-y-2 text-sm\">\n {recentBookings.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 <li\n key={booking.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-text-primary\">\n {booking.referenceCode} — {booking.itemName}\n </p>\n <p className=\"mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n {dateLabel ? (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarRange className=\"size-3\" 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\" aria-hidden=\"true\" />\n {booking.guests}\n </span>\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-3\">\n <span className=\"tabular-nums text-text-primary\">\n {booking.currency} {Number(booking.totalPrice).toFixed(2)}\n </span>\n <StatusBadge status={booking.status} />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard title={t('ggnomad.dashboard.sections.revenueByCategory', 'Revenue by category')}>\n {categories.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <span className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary\">\n <DollarSign className=\"size-5\" />\n </span>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.dashboard.empty.title', 'Revenue data coming soon')}\n </p>\n <p className=\"max-w-sm text-sm text-text-secondary\">\n {t(\n 'ggnomad.dashboard.empty.description',\n 'Revenue by category appears here as bookings come in across stays, tours and events.'\n )}\n </p>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {categories.map((category, index) => {\n const pct = maxCategory > 0 ? Math.max((category.value / maxCategory) * 100, 2) : 0;\n return (\n <li key={category.label}>\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {category.label}\n </span>\n <span className=\"flex-shrink-0 text-sm tabular-nums text-text-secondary\">\n ${category.value.toLocaleString()}\n </span>\n </div>\n <div className=\"mt-1.5 h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full transition-[width] duration-500 ease-out\"\n style={{\n width: `${pct}%`,\n backgroundColor: CHART_COLORS[index % CHART_COLORS.length],\n }}\n />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n </PageShell>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EntertainmentPage.js","names":[],"sources":["../../../src/ggnomad/pages/EntertainmentPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { CalendarClock, Leaf, MapPin, Ticket, Zap } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_EVENTS_QUERY } from '../graphql';\nimport { formatDateTime, humanizeEnum, jsonLabel } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport {\n EntityCard,\n EMPTY_LISTING_FILTERS,\n ListingFilters,\n LoadMoreButton,\n RatingSummary,\n hasActiveListingFilters,\n} from '../components';\nimport type { ListingFilterState } from '../components';\n\ntype EventRow = {\n id: string;\n name: string;\n category: string;\n startsAt: string;\n price: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images?: string[];\n venue?: unknown;\n instantBook: boolean;\n sustainabilityScore: number;\n};\n\nexport function EntertainmentPage() {\n const { t } = useI18n();\n const [limit, setLimit] = useState(12);\n const [filters, setFilters] = useState<ListingFilterState>(EMPTY_LISTING_FILTERS);\n\n const { data, loading, error } = useQuery(\n GGNOMAD_EVENTS_QUERY as TypedDocumentNode<\n { ggnomadEvents: EventRow[] },\n {\n limit: number;\n offset: number;\n status?: string;\n minSustainabilityScore?: number;\n hasAccessibilityFeatures?: string[];\n }\n >,\n {\n variables: {\n limit,\n offset: 0,\n status: filters.status,\n minSustainabilityScore: filters.minSustainabilityScore,\n hasAccessibilityFeatures:\n filters.accessibilityFeatures.length > 0 ? filters.accessibilityFeatures : undefined,\n },\n }\n );\n const events = data?.ggnomadEvents ?? [];\n const filtered = hasActiveListingFilters(filters);\n\n return (\n <PageShell\n title={t('ggnomad.entertainment.title', 'Entertainment')}\n titleAccent={t('ggnomad.entertainment.title.accent', 'Entertainment')}\n subtitle={t('ggnomad.entertainment.subtitle', 'Events and live experiences')}\n >\n <PagePurpose icon={<Ticket className=\"size-4\" />}>\n {t(\n 'ggnomad.entertainment.purpose',\n 'Find live events and experiences — concerts, shows and local happenings. Open an event to see its lineup, venue and timing, then book tickets for your trip dates.'\n )}\n </PagePurpose>\n\n <ListingFilters\n idPrefix=\"ggnomad-entertainment\"\n value={filters}\n onChange={(next) => {\n setLimit(12);\n setFilters(next);\n }}\n resultCount={events.length}\n />\n\n {error ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.entertainment.errorTitle', 'Could not load events')}\n description={error.message}\n />\n ) : events.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n loading\n ? t('ggnomad.entertainment.loadingTitle', 'Loading events…')\n : filtered\n ? t('ggnomad.entertainment.noMatchTitle', 'No events match these filters')\n : t('ggnomad.entertainment.emptyTitle', 'No events scheduled')\n }\n description={\n loading\n ? t('ggnomad.entertainment.loadingDescription', 'Fetching the live event catalogue.')\n : filtered\n ? t(\n 'ggnomad.entertainment.noMatchDescription',\n 'Try clearing a filter — status, sustainability score and accessibility all narrow the catalogue.'\n )\n : t(\n 'ggnomad.entertainment.emptyDescription',\n 'Upcoming events and live experiences will be listed here.'\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.entertainment.upcomingEvents', 'Upcoming events')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {events.map((event) => {\n const venueLabel = jsonLabel(event.venue);\n return (\n <EntityCard\n key={event.id}\n to={event.id}\n image={event.images?.[0]}\n imageAlt={event.name}\n title={event.name}\n meta={humanizeEnum(event.category)}\n badge={\n event.instantBook ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <Zap className=\"size-3\" aria-hidden=\"true\" />\n {t('ggnomad.listing.instantBook', 'Instant book')}\n </span>\n ) : null\n }\n price={`${event.currency} ${Number(event.price).toFixed(2)}`}\n footer={\n <div className=\"space-y-1\">\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDateTime(event.startsAt) ?? event.startsAt}\n </span>\n {venueLabel ? (\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"size-3.5\" aria-hidden=\"true\" />\n {venueLabel}\n </span>\n ) : null}\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n <RatingSummary rating={event.rating} reviewCount={event.reviewCount} />\n {event.sustainabilityScore > 0 ? (\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 {event.sustainabilityScore}\n </span>\n ) : null}\n </span>\n </div>\n }\n />\n );\n })}\n </div>\n <LoadMoreButton\n visible={events.length >= limit}\n onLoadMore={() => setLimit((l) => l + 12)}\n />\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EntertainmentPage.js","names":[],"sources":["../../../src/ggnomad/pages/EntertainmentPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { CalendarClock, Leaf, MapPin, Ticket, Zap } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_EVENTS_QUERY } from '../graphql';\nimport { formatDateTime, humanizeEnum, jsonLabel } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport {\n EntityCard,\n EMPTY_LISTING_FILTERS,\n ListingFilters,\n LoadMoreButton,\n RatingSummary,\n hasActiveListingFilters,\n} from '../components';\nimport type { ListingFilterState } from '../components';\n\ntype EventRow = {\n id: string;\n name: string;\n category: string;\n startsAt: string;\n price: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images?: string[];\n venue?: unknown;\n instantBook: boolean;\n sustainabilityScore: number;\n};\n\nexport function EntertainmentPage() {\n const { t } = useI18n();\n const [limit, setLimit] = useState(12);\n const [filters, setFilters] = useState<ListingFilterState>(EMPTY_LISTING_FILTERS);\n\n const { data, loading, error } = useQuery(\n GGNOMAD_EVENTS_QUERY as TypedDocumentNode<\n { ggnomadEvents: EventRow[] },\n {\n limit: number;\n offset: number;\n status?: string;\n minSustainabilityScore?: number;\n hasAccessibilityFeatures?: string[];\n }\n >,\n {\n variables: {\n limit,\n offset: 0,\n status: filters.status,\n minSustainabilityScore: filters.minSustainabilityScore,\n hasAccessibilityFeatures:\n filters.accessibilityFeatures.length > 0 ? filters.accessibilityFeatures : undefined,\n },\n }\n );\n const events = data?.ggnomadEvents ?? [];\n const filtered = hasActiveListingFilters(filters);\n\n return (\n <PageShell\n title={t('ggnomad.entertainment.title', 'Entertainment')}\n titleAccent={t('ggnomad.entertainment.title.accent', 'Entertainment')}\n subtitle={t('ggnomad.entertainment.subtitle', 'Events and live experiences')}\n >\n <PagePurpose icon={<Ticket className=\"size-4\" />}>\n {t(\n 'ggnomad.entertainment.purpose',\n 'Find live events and experiences — concerts, shows and local happenings. Open an event to see its lineup, venue and timing, then book tickets for your trip dates.'\n )}\n </PagePurpose>\n\n <ListingFilters\n idPrefix=\"ggnomad-entertainment\"\n value={filters}\n onChange={(next) => {\n setLimit(12);\n setFilters(next);\n }}\n resultCount={events.length}\n />\n\n {error ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.entertainment.errorTitle', 'Could not load events')}\n description={error.message}\n />\n ) : events.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n loading\n ? t('ggnomad.entertainment.loadingTitle', 'Loading events…')\n : filtered\n ? t('ggnomad.entertainment.noMatchTitle', 'No events match these filters')\n : t('ggnomad.entertainment.emptyTitle', 'No events scheduled')\n }\n description={\n loading\n ? t('ggnomad.entertainment.loadingDescription', 'Fetching the live event catalogue.')\n : filtered\n ? t(\n 'ggnomad.entertainment.noMatchDescription',\n 'Try clearing a filter — status, sustainability score and accessibility all narrow the catalogue.'\n )\n : t(\n 'ggnomad.entertainment.emptyDescription',\n 'Upcoming events and live experiences will be listed here.'\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.entertainment.upcomingEvents', 'Upcoming events')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {events.map((event) => {\n const venueLabel = jsonLabel(event.venue);\n return (\n <EntityCard\n key={event.id}\n to={event.id}\n image={event.images?.[0]}\n imageAlt={event.name}\n title={event.name}\n meta={humanizeEnum(event.category)}\n badge={\n event.instantBook ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <Zap className=\"size-3\" aria-hidden=\"true\" />\n {t('ggnomad.listing.instantBook', 'Instant book')}\n </span>\n ) : null\n }\n price={`${event.currency} ${Number(event.price).toFixed(2)}`}\n footer={\n <div className=\"space-y-1\">\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDateTime(event.startsAt) ?? event.startsAt}\n </span>\n {venueLabel ? (\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"size-3.5\" aria-hidden=\"true\" />\n {venueLabel}\n </span>\n ) : null}\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n <RatingSummary rating={event.rating} reviewCount={event.reviewCount} />\n {event.sustainabilityScore > 0 ? (\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 {event.sustainabilityScore}\n </span>\n ) : null}\n </span>\n </div>\n }\n />\n );\n })}\n </div>\n <LoadMoreButton\n visible={events.length >= limit}\n onLoadMore={() => setLimit((l) => l + 12)}\n />\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}