@burdenoff/microfe-ggnomad 2026.825.1 → 2026.828.1

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 (98) hide show
  1. package/dist/ggnomad/components/AvailabilityPanel.d.ts +15 -0
  2. package/dist/ggnomad/components/AvailabilityPanel.d.ts.map +1 -0
  3. package/dist/ggnomad/components/AvailabilityPanel.js +91 -0
  4. package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -0
  5. package/dist/ggnomad/components/BookingPanel.d.ts +1 -3
  6. package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
  7. package/dist/ggnomad/components/BookingPanel.js +91 -82
  8. package/dist/ggnomad/components/BookingPanel.js.map +1 -1
  9. package/dist/ggnomad/components/EcoAccessPanel.d.ts +17 -0
  10. package/dist/ggnomad/components/EcoAccessPanel.d.ts.map +1 -0
  11. package/dist/ggnomad/components/EcoAccessPanel.js +68 -0
  12. package/dist/ggnomad/components/EcoAccessPanel.js.map +1 -0
  13. package/dist/ggnomad/components/FactGrid.d.ts +18 -0
  14. package/dist/ggnomad/components/FactGrid.d.ts.map +1 -0
  15. package/dist/ggnomad/components/FactGrid.js +29 -0
  16. package/dist/ggnomad/components/FactGrid.js.map +1 -0
  17. package/dist/ggnomad/components/GroupTripPanel.d.ts +22 -0
  18. package/dist/ggnomad/components/GroupTripPanel.d.ts.map +1 -0
  19. package/dist/ggnomad/components/GroupTripPanel.js +350 -0
  20. package/dist/ggnomad/components/GroupTripPanel.js.map +1 -0
  21. package/dist/ggnomad/components/ListingFilters.d.ts +29 -0
  22. package/dist/ggnomad/components/ListingFilters.d.ts.map +1 -0
  23. package/dist/ggnomad/components/ListingFilters.js +164 -0
  24. package/dist/ggnomad/components/ListingFilters.js.map +1 -0
  25. package/dist/ggnomad/components/PhotoGallery.d.ts +15 -0
  26. package/dist/ggnomad/components/PhotoGallery.d.ts.map +1 -0
  27. package/dist/ggnomad/components/PhotoGallery.js +54 -0
  28. package/dist/ggnomad/components/PhotoGallery.js.map +1 -0
  29. package/dist/ggnomad/components/ProviderCard.d.ts +19 -0
  30. package/dist/ggnomad/components/ProviderCard.d.ts.map +1 -0
  31. package/dist/ggnomad/components/ProviderCard.js +66 -0
  32. package/dist/ggnomad/components/ProviderCard.js.map +1 -0
  33. package/dist/ggnomad/components/RatingSummary.d.ts +14 -0
  34. package/dist/ggnomad/components/RatingSummary.d.ts.map +1 -0
  35. package/dist/ggnomad/components/RatingSummary.js +32 -0
  36. package/dist/ggnomad/components/RatingSummary.js.map +1 -0
  37. package/dist/ggnomad/components/TagChips.d.ts +16 -0
  38. package/dist/ggnomad/components/TagChips.d.ts.map +1 -0
  39. package/dist/ggnomad/components/TagChips.js +20 -0
  40. package/dist/ggnomad/components/TagChips.js.map +1 -0
  41. package/dist/ggnomad/components/index.d.ts +13 -0
  42. package/dist/ggnomad/components/index.d.ts.map +1 -1
  43. package/dist/ggnomad/components/index.js +9 -0
  44. package/dist/ggnomad/graphql.d.ts +10 -0
  45. package/dist/ggnomad/graphql.d.ts.map +1 -1
  46. package/dist/ggnomad/graphql.js +308 -20
  47. package/dist/ggnomad/graphql.js.map +1 -1
  48. package/dist/ggnomad/pages/ActivityDetailsPage.d.ts.map +1 -1
  49. package/dist/ggnomad/pages/ActivityDetailsPage.js +174 -86
  50. package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
  51. package/dist/ggnomad/pages/AnalyticsPage.d.ts.map +1 -1
  52. package/dist/ggnomad/pages/AnalyticsPage.js +109 -77
  53. package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
  54. package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
  55. package/dist/ggnomad/pages/BookingsPage.js +188 -137
  56. package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
  57. package/dist/ggnomad/pages/DashboardPage.d.ts.map +1 -1
  58. package/dist/ggnomad/pages/DashboardPage.js +245 -128
  59. package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
  60. package/dist/ggnomad/pages/EntertainmentPage.d.ts.map +1 -1
  61. package/dist/ggnomad/pages/EntertainmentPage.js +115 -53
  62. package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
  63. package/dist/ggnomad/pages/EventDetailsPage.d.ts.map +1 -1
  64. package/dist/ggnomad/pages/EventDetailsPage.js +170 -87
  65. package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
  66. package/dist/ggnomad/pages/FavoritesPage.d.ts.map +1 -1
  67. package/dist/ggnomad/pages/FavoritesPage.js +201 -84
  68. package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
  69. package/dist/ggnomad/pages/HospitalityPage.d.ts.map +1 -1
  70. package/dist/ggnomad/pages/HospitalityPage.js +99 -56
  71. package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
  72. package/dist/ggnomad/pages/PropertyDetailsPage.d.ts.map +1 -1
  73. package/dist/ggnomad/pages/PropertyDetailsPage.js +177 -93
  74. package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
  75. package/dist/ggnomad/pages/ProviderPage.d.ts.map +1 -1
  76. package/dist/ggnomad/pages/ProviderPage.js +112 -63
  77. package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
  78. package/dist/ggnomad/pages/SearchPage.d.ts.map +1 -1
  79. package/dist/ggnomad/pages/SearchPage.js +236 -65
  80. package/dist/ggnomad/pages/SearchPage.js.map +1 -1
  81. package/dist/ggnomad/pages/TourDetailsPage.d.ts.map +1 -1
  82. package/dist/ggnomad/pages/TourDetailsPage.js +171 -86
  83. package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
  84. package/dist/ggnomad/pages/TourismPage.d.ts.map +1 -1
  85. package/dist/ggnomad/pages/TourismPage.js +108 -54
  86. package/dist/ggnomad/pages/TourismPage.js.map +1 -1
  87. package/dist/ggnomad/pages/TravelPage.d.ts.map +1 -1
  88. package/dist/ggnomad/pages/TravelPage.js +406 -43
  89. package/dist/ggnomad/pages/TravelPage.js.map +1 -1
  90. package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
  91. package/dist/ggnomad/pages/TripPlanDetailPage.js +93 -70
  92. package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
  93. package/dist/ggnomad/utils/format.d.ts +47 -0
  94. package/dist/ggnomad/utils/format.d.ts.map +1 -0
  95. package/dist/ggnomad/utils/format.js +125 -0
  96. package/dist/ggnomad/utils/format.js.map +1 -0
  97. package/dist/stories/BookingPanel.stories.d.ts.map +1 -1
  98. package/package.json +1 -1
@@ -1,36 +1,39 @@
1
1
  import { CANCEL_GGNOMAD_BOOKING_MUTATION as e, CREATE_GGNOMAD_BOOKING_MUTATION as t, GGNOMAD_BOOKABLE_SAMPLE_QUERY as n, GGNOMAD_BOOKINGS_QUERY as r } from "../graphql.js";
2
- import { PageShell as i } from "./PageShell.js";
3
- import { StatusBadge as a } from "../components/StatusBadge.js";
4
- import { EntityCard as o } from "../components/EntityCard.js";
5
- import { notify as s } from "../utils/notify.js";
6
- import { TicketDialog as c } from "../components/TicketDialog.js";
7
- import { RedeemTicketPanel as l } from "../components/RedeemTicketPanel.js";
2
+ import { formatDate as i, humanizeEnum as a } from "../utils/format.js";
3
+ import { PageShell as o } from "./PageShell.js";
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";
8
10
  import "../components/index.js";
9
- import { useMemo as u, useState as d } from "react";
10
- import { CalendarCheck as f, Compass as p, Hotel as m, Plus as h, QrCode as g, Ticket as _, XCircle as v } from "lucide-react";
11
- import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
- import { useNavigate as b } from "react-router";
13
- import { useMutation as x, useQuery as S } from "@apollo/client/react";
14
- import { AlertDialog as C, AlertDialogAction as w, AlertDialogCancel as T, AlertDialogContent as E, AlertDialogDescription as D, AlertDialogFooter as O, AlertDialogHeader as k, AlertDialogTitle as A, CTAOverflowMenu as j, GlassCard as M, IllustratedEmptyState as N, NextSteps as P, PagePurpose as F } from "@burdenoff/fe-libs/ui";
15
- import { jsx as I, jsxs as L } from "react/jsx-runtime";
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";
17
+ import { jsx as R, jsxs as z } from "react/jsx-runtime";
16
18
  //#region src/ggnomad/pages/BookingsPage.tsx
17
- function R() {
18
- let { t: R } = y(), z = b(), [B, V] = d(null), [H, U] = d(null), { data: W, refetch: G } = S(r, { variables: {
19
+ 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: {
19
21
  limit: 20,
20
- offset: 0
21
- } }), { data: K } = S(n), [q, { loading: J }] = x(t), [Y, { loading: X }] = x(e), Z = u(() => W?.ggnomadBookings ?? [], [W?.ggnomadBookings]), Q = u(() => {
22
+ offset: 0,
23
+ 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(() => {
22
25
  let e = {
23
26
  CONFIRMED: 0,
24
27
  CHECKED_IN: 0,
25
28
  COMPLETED: 0,
26
29
  CANCELLED: 0
27
30
  };
28
- return Z.forEach((t) => {
31
+ return Q.forEach((t) => {
29
32
  let n = t.status?.toUpperCase();
30
33
  n && n in e && (e[n] += 1);
31
34
  }), e;
32
- }, [Z]), $ = () => {
33
- let e = K?.ggnomadProperties?.[0];
35
+ }, [Q]), se = () => {
36
+ let e = X?.ggnomadProperties?.[0];
34
37
  if (e) return {
35
38
  type: "PROPERTY",
36
39
  listing: {
@@ -40,34 +43,34 @@ function R() {
40
43
  currency: e.currency
41
44
  }
42
45
  };
43
- let t = K?.ggnomadTours?.[0];
46
+ let t = X?.ggnomadTours?.[0];
44
47
  if (t) return {
45
48
  type: "TOUR",
46
49
  listing: t
47
50
  };
48
- let n = K?.ggnomadEvents?.[0];
51
+ let n = X?.ggnomadEvents?.[0];
49
52
  if (n) return {
50
53
  type: "EVENT",
51
54
  listing: n
52
55
  };
53
- let r = K?.ggnomadActivities?.[0];
56
+ let r = X?.ggnomadActivities?.[0];
54
57
  return r ? {
55
58
  type: "ACTIVITY",
56
59
  listing: r
57
60
  } : null;
58
- }, ee = async () => {
59
- let e = $();
61
+ }, ce = async () => {
62
+ let e = se();
60
63
  if (!e) {
61
- s.warning(R("ggnomad.bookings.toast.noListingsTitle", "No listings available to book yet"), R("ggnomad.bookings.toast.noListingsBody", "Add a property, tour, event or activity first, then create a booking against it."));
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."));
62
65
  return;
63
66
  }
64
67
  let { type: t, listing: n } = e;
65
68
  try {
66
- let e = await q({ variables: { input: {
69
+ let e = await ie({ variables: { input: {
67
70
  bookingType: t,
68
71
  itemId: n.id,
69
72
  itemName: n.name,
70
- guestName: R("ggnomad.bookings.demo.guestName", "Demo Guest"),
73
+ guestName: B("ggnomad.bookings.demo.guestName", "Demo Guest"),
71
74
  guestEmail: "demo@burdenoff.com",
72
75
  guests: 2,
73
76
  totalPrice: Number(n.price ?? 0),
@@ -75,194 +78,242 @@ function R() {
75
78
  paymentMethod: "card"
76
79
  } } });
77
80
  if (e.error) {
78
- s.error(R("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
81
+ u.error(B("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
79
82
  return;
80
83
  }
81
84
  let r = e.data?.createGgnomadBooking;
82
- s.success(R("ggnomad.bookings.toast.createSuccessTitle", "Booking confirmed"), r ? R("ggnomad.bookings.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : n.name), await G();
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();
83
86
  } catch (e) {
84
- s.error(R("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
87
+ u.error(B("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
85
88
  }
86
- }, te = async () => {
87
- if (B) try {
88
- let e = await Y({ variables: {
89
- id: B.id,
90
- reason: R("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
89
+ }, le = async () => {
90
+ if (H) try {
91
+ let e = await oe({ variables: {
92
+ id: H.id,
93
+ reason: B("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
91
94
  } });
92
95
  if (e.error) {
93
- s.error(R("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
96
+ u.error(B("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
94
97
  return;
95
98
  }
96
- s.success(R("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), B.referenceCode), await G();
99
+ u.success(B("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), H.referenceCode), await Y();
97
100
  } catch (e) {
98
- s.error(R("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
101
+ u.error(B("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
99
102
  } finally {
100
- V(null);
103
+ U(null);
101
104
  }
102
105
  };
103
- return /* @__PURE__ */ L(i, {
104
- title: R("ggnomad.bookings.title", "Bookings"),
105
- titleAccent: R("ggnomad.bookings.title.accent", "Bookings"),
106
- subtitle: R("ggnomad.bookings.subtitle", "End-to-end booking operations with real API mutations"),
107
- actions: /* @__PURE__ */ I(j, {
106
+ return /* @__PURE__ */ z(o, {
107
+ title: B("ggnomad.bookings.title", "Bookings"),
108
+ titleAccent: B("ggnomad.bookings.title.accent", "Bookings"),
109
+ subtitle: B("ggnomad.bookings.subtitle", "End-to-end booking operations with real API mutations"),
110
+ actions: /* @__PURE__ */ R(N, {
108
111
  primary: {
109
- label: R("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
110
- onSelect: () => void ee(),
111
- disabled: J,
112
- icon: /* @__PURE__ */ I(h, {})
112
+ label: B("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
113
+ onSelect: () => void ce(),
114
+ disabled: ae,
115
+ icon: /* @__PURE__ */ R(b, {})
113
116
  },
114
117
  actions: []
115
118
  }),
116
119
  children: [
117
- /* @__PURE__ */ I(F, {
118
- icon: /* @__PURE__ */ I(f, { className: "size-4" }),
119
- children: R("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.")
120
+ /* @__PURE__ */ R(L, {
121
+ icon: /* @__PURE__ */ R(h, { className: "size-4" }),
122
+ 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.")
120
123
  }),
121
- /* @__PURE__ */ I(P, {
124
+ /* @__PURE__ */ R(I, {
122
125
  storageKey: "ggnomad-bookings",
123
126
  steps: [
124
127
  {
125
128
  id: "book-stay",
126
- label: R("ggnomad.bookings.next.stay", "Book a place to stay"),
127
- description: R("ggnomad.bookings.next.stayDesc", "Browse hospitality listings and reserve a property."),
128
- icon: /* @__PURE__ */ I(m, { className: "size-4" }),
129
- onClick: () => z("../hospitality")
129
+ label: B("ggnomad.bookings.next.stay", "Book a place to stay"),
130
+ description: B("ggnomad.bookings.next.stayDesc", "Browse hospitality listings and reserve a property."),
131
+ icon: /* @__PURE__ */ R(v, { className: "size-4" }),
132
+ onClick: () => V("../hospitality")
130
133
  },
131
134
  {
132
135
  id: "book-tour",
133
- label: R("ggnomad.bookings.next.tour", "Add a tour or activity"),
134
- description: R("ggnomad.bookings.next.tourDesc", "Pick a curated experience from Tourism."),
135
- icon: /* @__PURE__ */ I(p, { className: "size-4" }),
136
- onClick: () => z("../tourism")
136
+ label: B("ggnomad.bookings.next.tour", "Add a tour or activity"),
137
+ description: B("ggnomad.bookings.next.tourDesc", "Pick a curated experience from Tourism."),
138
+ icon: /* @__PURE__ */ R(_, { className: "size-4" }),
139
+ onClick: () => V("../tourism")
137
140
  },
138
141
  {
139
142
  id: "book-event",
140
- label: R("ggnomad.bookings.next.event", "Grab event tickets"),
141
- description: R("ggnomad.bookings.next.eventDesc", "Find live events to round out the itinerary."),
142
- icon: /* @__PURE__ */ I(_, { className: "size-4" }),
143
- onClick: () => z("../entertainment")
143
+ label: B("ggnomad.bookings.next.event", "Grab event tickets"),
144
+ description: B("ggnomad.bookings.next.eventDesc", "Find live events to round out the itinerary."),
145
+ icon: /* @__PURE__ */ R(S, { className: "size-4" }),
146
+ onClick: () => V("../entertainment")
144
147
  }
145
148
  ]
146
149
  }),
147
- /* @__PURE__ */ I(l, { onRedeemed: () => void G() }),
148
- Z.length > 0 && /* @__PURE__ */ I("div", {
150
+ /* @__PURE__ */ R(f, { onRedeemed: () => void Y() }),
151
+ Q.length > 0 || K ? /* @__PURE__ */ R("div", {
149
152
  className: "grid gap-3 sm:grid-cols-4",
150
153
  children: [
151
154
  {
152
155
  status: "CONFIRMED",
153
- label: R("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
154
- icon: f
156
+ label: B("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
157
+ icon: h
155
158
  },
156
159
  {
157
160
  status: "CHECKED_IN",
158
- label: R("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
159
- icon: m
161
+ label: B("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
162
+ icon: v
160
163
  },
161
164
  {
162
165
  status: "COMPLETED",
163
- label: R("ggnomad.bookings.pipeline.completed", "Completed"),
164
- icon: _
166
+ label: B("ggnomad.bookings.pipeline.completed", "Completed"),
167
+ icon: S
165
168
  },
166
169
  {
167
170
  status: "CANCELLED",
168
- label: R("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
169
- icon: v
171
+ label: B("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
172
+ icon: w
170
173
  }
171
174
  ].map((e) => {
172
- let t = e.icon, n = Q[e.status];
173
- return /* @__PURE__ */ L("div", {
174
- className: "flex flex-col items-center gap-2 rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-center",
175
+ let t = e.icon, n = K === e.status, r = $[e.status];
176
+ return /* @__PURE__ */ z("button", {
177
+ type: "button",
178
+ "aria-pressed": n,
179
+ onClick: () => q(n ? null : e.status),
180
+ 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"),
175
181
  children: [
176
- /* @__PURE__ */ I("div", {
182
+ /* @__PURE__ */ R("div", {
177
183
  className: "flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary",
178
- children: /* @__PURE__ */ I(t, { className: "size-4" })
184
+ children: /* @__PURE__ */ R(t, { className: "size-4" })
179
185
  }),
180
- /* @__PURE__ */ I("p", {
186
+ /* @__PURE__ */ R("p", {
181
187
  className: "text-xs font-medium text-text-secondary",
182
188
  children: e.label
183
189
  }),
184
- /* @__PURE__ */ I("p", {
190
+ /* @__PURE__ */ R("p", {
185
191
  className: "text-lg font-semibold tabular-nums text-text-primary",
186
- children: n
192
+ children: r
187
193
  })
188
194
  ]
189
195
  }, e.status);
190
196
  })
191
- }),
192
- Z.length === 0 ? /* @__PURE__ */ I(N, {
197
+ }) : null,
198
+ K ? /* @__PURE__ */ z("div", {
199
+ className: "flex flex-wrap items-center gap-3 text-sm",
200
+ children: [/* @__PURE__ */ R("span", {
201
+ className: "text-text-secondary",
202
+ children: B("ggnomad.bookings.filteredBy", "Showing {{status}} bookings only").replace("{{status}}", a(K).toLowerCase())
203
+ }), /* @__PURE__ */ R("button", {
204
+ type: "button",
205
+ onClick: () => q(null),
206
+ className: "font-medium text-action-primary-bg hover:underline",
207
+ children: B("ggnomad.bookings.clearFilter", "Show all bookings")
208
+ })]
209
+ }) : null,
210
+ Q.length === 0 ? /* @__PURE__ */ R(F, {
193
211
  illustration: "empty-data",
194
- title: R("ggnomad.bookings.emptyTitle", "No bookings yet"),
195
- description: R("ggnomad.bookings.emptyDescription", "Create your first booking to see it tracked here in real time.")
196
- }) : /* @__PURE__ */ L(M, {
212
+ title: K ? B("ggnomad.bookings.noneForStatusTitle", "No {{status}} bookings").replace("{{status}}", a(K).toLowerCase()) : B("ggnomad.bookings.emptyTitle", "No bookings yet"),
213
+ description: K ? B("ggnomad.bookings.noneForStatusDescription", "Nothing is in this stage right now — clear the filter to see every booking.") : B("ggnomad.bookings.emptyDescription", "Create your first booking to see it tracked here in real time.")
214
+ }) : /* @__PURE__ */ z(P, {
197
215
  treatment: "glass",
198
216
  glow: !0,
199
217
  className: "border-border-seam p-5",
200
- children: [/* @__PURE__ */ I("h2", {
218
+ children: [/* @__PURE__ */ R("h2", {
201
219
  className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
202
- children: R("ggnomad.bookings.activeBookings", "Your bookings")
203
- }), /* @__PURE__ */ I("div", {
220
+ children: B("ggnomad.bookings.activeBookings", "Your bookings")
221
+ }), /* @__PURE__ */ R("div", {
204
222
  className: "grid gap-3 sm:grid-cols-2",
205
- children: Z.map((e) => /* @__PURE__ */ I(o, {
206
- title: e.referenceCode,
207
- meta: e.itemName,
208
- price: `${e.currency} ${Number(e.totalPrice).toFixed(2)}`,
209
- badge: /* @__PURE__ */ I(a, {
210
- status: e.status,
211
- live: e.status !== "CANCELLED"
212
- }),
213
- footer: e.status === "CANCELLED" ? null : /* @__PURE__ */ I(j, {
214
- align: "end",
215
- actions: [{
216
- label: R("ggnomad.bookings.actions.viewTicket", "View mobile ticket"),
217
- onSelect: () => U({
218
- id: e.id,
219
- referenceCode: e.referenceCode,
220
- status: e.status
221
- }),
222
- icon: /* @__PURE__ */ I(g, {})
223
- }, {
224
- label: R("ggnomad.bookings.actions.cancel", "Cancel booking"),
225
- onSelect: () => V({
226
- id: e.id,
227
- referenceCode: e.referenceCode
228
- }),
229
- intent: "destructive",
230
- icon: /* @__PURE__ */ I(v, {})
231
- }]
232
- })
233
- }, e.id))
223
+ children: Q.map((e) => {
224
+ 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, {
226
+ title: e.referenceCode,
227
+ meta: e.itemName,
228
+ price: `${e.currency} ${Number(e.totalPrice).toFixed(2)}`,
229
+ badge: /* @__PURE__ */ R(c, {
230
+ status: e.status,
231
+ live: e.status !== "CANCELLED"
232
+ }),
233
+ footer: /* @__PURE__ */ z("div", {
234
+ className: "space-y-2",
235
+ children: [/* @__PURE__ */ z("div", {
236
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary",
237
+ children: [
238
+ /* @__PURE__ */ R("span", { children: a(e.bookingType) }),
239
+ o ? /* @__PURE__ */ z("span", {
240
+ className: "inline-flex items-center gap-1",
241
+ children: [/* @__PURE__ */ R(g, {
242
+ className: "size-3.5",
243
+ "aria-hidden": "true"
244
+ }), o]
245
+ }) : null,
246
+ /* @__PURE__ */ z("span", {
247
+ className: "inline-flex items-center gap-1 tabular-nums",
248
+ children: [/* @__PURE__ */ R(C, {
249
+ className: "size-3.5",
250
+ "aria-hidden": "true"
251
+ }), e.guests === 1 ? B("ggnomad.bookings.oneGuest", "1 guest") : B("ggnomad.bookings.guests", "{{n}} guests").replace("{{n}}", String(e.guests))]
252
+ }),
253
+ /* @__PURE__ */ R("span", { children: B("ggnomad.bookings.payment", "Payment: {{status}}").replace("{{status}}", a(e.paymentStatus).toLowerCase()) }),
254
+ e.carbonEstimateKg !== null && e.carbonEstimateKg !== void 0 ? /* @__PURE__ */ z("span", {
255
+ className: "inline-flex items-center gap-1 tabular-nums text-status-success-text",
256
+ children: [/* @__PURE__ */ R(y, {
257
+ className: "size-3.5",
258
+ "aria-hidden": "true"
259
+ }), B("ggnomad.bookings.carbonEstimate", "≈{{kg}} kg CO₂e est.").replace("{{kg}}", e.carbonEstimateKg.toLocaleString())]
260
+ }) : null
261
+ ]
262
+ }), e.status === "CANCELLED" ? null : /* @__PURE__ */ R(N, {
263
+ align: "end",
264
+ actions: [{
265
+ label: B("ggnomad.bookings.actions.viewTicket", "View mobile ticket"),
266
+ onSelect: () => G({
267
+ id: e.id,
268
+ referenceCode: e.referenceCode,
269
+ status: e.status
270
+ }),
271
+ icon: /* @__PURE__ */ R(x, {})
272
+ }, {
273
+ label: B("ggnomad.bookings.actions.cancel", "Cancel booking"),
274
+ onSelect: () => U({
275
+ id: e.id,
276
+ referenceCode: e.referenceCode
277
+ }),
278
+ intent: "destructive",
279
+ icon: /* @__PURE__ */ R(w, {})
280
+ }]
281
+ })]
282
+ })
283
+ }, e.id);
284
+ })
234
285
  })]
235
286
  }),
236
- /* @__PURE__ */ I(C, {
237
- open: B !== null,
287
+ /* @__PURE__ */ R(ee, {
288
+ open: H !== null,
238
289
  onOpenChange: (e) => {
239
- e || V(null);
290
+ e || U(null);
240
291
  },
241
- children: /* @__PURE__ */ L(E, { children: [/* @__PURE__ */ L(k, { children: [/* @__PURE__ */ I(A, { children: R("ggnomad.bookings.cancelDialog.title", "Cancel this booking?") }), /* @__PURE__ */ I(D, { children: R("ggnomad.bookings.cancelDialog.body", "This releases reservation {{ref}} and cannot be undone.").replace("{{ref}}", B?.referenceCode ?? "") })] }), /* @__PURE__ */ L(O, { children: [/* @__PURE__ */ I(T, {
242
- disabled: X,
243
- children: R("ggnomad.bookings.cancelDialog.keep", "Keep booking")
244
- }), /* @__PURE__ */ I(w, {
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, {
293
+ disabled: Z,
294
+ children: B("ggnomad.bookings.cancelDialog.keep", "Keep booking")
295
+ }), /* @__PURE__ */ R(te, {
245
296
  className: "bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover",
246
- disabled: X,
297
+ disabled: Z,
247
298
  onClick: (e) => {
248
- e.preventDefault(), te();
299
+ e.preventDefault(), le();
249
300
  },
250
- children: X ? R("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : R("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
301
+ children: Z ? B("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : B("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
251
302
  })] })] })
252
303
  }),
253
- H ? /* @__PURE__ */ I(c, {
254
- open: H !== null,
304
+ W ? /* @__PURE__ */ R(d, {
305
+ open: W !== null,
255
306
  onOpenChange: (e) => {
256
- e || U(null);
307
+ e || G(null);
257
308
  },
258
- bookingId: H.id,
259
- referenceCode: H.referenceCode,
260
- bookingStatus: H.status
309
+ bookingId: W.id,
310
+ referenceCode: W.referenceCode,
311
+ bookingStatus: W.status
261
312
  }) : null
262
313
  ]
263
314
  });
264
315
  }
265
316
  //#endregion
266
- export { R as BookingsPage };
317
+ export { B as BookingsPage };
267
318
 
268
319
  //# 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 { CalendarCheck, Compass, Hotel, Plus, QrCode, Ticket, XCircle } 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 { PageShell } from './PageShell';\nimport { EntityCard, RedeemTicketPanel, StatusBadge, TicketDialog } from '../components';\nimport { notify } from '../utils/notify';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\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\n const { data, refetch } = useQuery(\n GGNOMAD_BOOKINGS_QUERY as TypedDocumentNode<\n {\n ggnomadBookings: Array<{\n id: string;\n referenceCode: string;\n itemName: string;\n status: string;\n totalPrice: number;\n currency: string;\n }>;\n },\n { limit: number; offset: number }\n >,\n { variables: { limit: 20, offset: 0 } }\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 && (\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 count = statusCounts[stage.status];\n return (\n <div\n key={stage.status}\n className=\"flex flex-col items-center gap-2 rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-center\"\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 </div>\n );\n })}\n </div>\n )}\n\n {bookings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.bookings.emptyTitle', 'No bookings yet')}\n description={t(\n 'ggnomad.bookings.emptyDescription',\n 'Create your first booking to see it tracked here in real time.'\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 <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 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 }\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":";;;;;;;;;;;;;;;;AAwCA,SAAgB,IAAe;CAC7B,IAAM,EAAE,SAAM,EAAQ,GAChB,IAAW,EAAY,GACvB,CAAC,GAAc,KAAmB,EAA8B,IAAI,GACpE,CAAC,GAAc,KAAmB,EAA8B,IAAI,GAEpE,EAAE,SAAM,eAAY,EACxB,GAaA,EAAE,WAAW;EAAE,OAAO;EAAI,QAAQ;CAAE,EAAE,CACxC,GAIM,EAAE,MAAM,MAAe,EAC3B,CAcF,GAEM,CAAC,GAAe,EAAE,SAAS,OAAc,EAC7C,CAgBF,GAEM,CAAC,GAAe,EAAE,SAAS,OAAgB,EAC/C,CAIF,GAEM,IAAW,QAAc,GAAM,mBAAmB,CAAC,GAAG,CAAC,GAAM,eAAe,CAAC,GAE7E,IAAe,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,UAAgF;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,KAA0B,YAAY;EAC1C,IAAM,IAAS,EAAkB;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,EAAc,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;GACjB,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,EAC/D,QAAQ,WAAW,EAAQ,aAAa,EACxC,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,EAAc,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,GAAwB;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,KACjB,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,EAAE,KAAK,MAAU;KACf,IAAM,IAAO,EAAM,MACb,IAAQ,EAAa,EAAM;KACjC,OACE,kBAAC,OAAD;MAEE,WAAU;gBAFZ;OAIE,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;MAC3E;QARE,EAAM,MAQR;IAET,CAAC;GACE,CAAA;GAGN,EAAS,WAAW,IACnB,kBAAC,GAAD;IACE,cAAa;IACb,OAAO,EAAE,+BAA+B,iBAAiB;IACzD,aAAa,EACX,qCACA,gEACF;GACD,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,MACb,kBAAC,GAAD;MAEE,OAAO,EAAQ;MACf,MAAM,EAAQ;MACd,OAAO,GAAG,EAAQ,SAAS,GAAG,OAAO,EAAQ,UAAU,EAAE,QAAQ,CAAC;MAClE,OACE,kBAAC,GAAD;OAAa,QAAQ,EAAQ;OAAQ,MAAM,EAAQ,WAAW;MAAc,CAAA;MAE9E,QACE,EAAQ,WAAW,cA0Bf,OAzBF,kBAAC,GAAD;OACE,OAAM;OACN,SAAS,CACP;QACE,OAAO,EAAE,uCAAuC,oBAAoB;QACpE,gBACE,EAAgB;SACd,IAAI,EAAQ;SACZ,eAAe,EAAQ;SACvB,QAAQ,EAAQ;QAClB,CAAC;QACH,MAAM,kBAAC,GAAD,CAAS,CAAA;OACjB,GACA;QACE,OAAO,EAAE,mCAAmC,gBAAgB;QAC5D,gBACE,EAAgB;SACd,IAAI,EAAQ;SACZ,eAAe,EAAQ;QACzB,CAAC;QACH,QAAQ;QACR,MAAM,kBAAC,GAAD,CAAU,CAAA;OAClB,CACF;MACD,CAAA;KAGN,GApCM,EAAQ,EAoCd,CACF;IACE,CAAA,CACI;;GAGb,kBAAC,GAAD;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,EAAE,QAAQ,WAAW,GAAc,iBAAiB,EAAE,EAChC,CAAA,CACP,EAAA,CAAA,GACnB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAmB,UAAU;eAC1B,EAAE,sCAAsC,cAAc;IACtC,CAAA,GACnB,kBAAC,GAAD;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';\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 +1 @@
1
- {"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/DashboardPage.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,aAAa,gCAgR5B"}
1
+ {"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/DashboardPage.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,aAAa,gCA0Y5B"}