@burdenoff/microfe-ggnomad 2026.705.3 → 2026.705.5

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.
@@ -4,20 +4,31 @@ import { StatusBadge as a } from "../components/StatusBadge.js";
4
4
  import { EntityCard as o } from "../components/EntityCard.js";
5
5
  import { notify as s } from "../utils/notify.js";
6
6
  import "../components/index.js";
7
- import { useState as c } from "react";
8
- import { CalendarCheck as l, Compass as u, Hotel as d, Plus as f, Ticket as p, XCircle as m } from "lucide-react";
9
- import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
- import { useNavigate as g } from "react-router-dom";
11
- import { useMutation as _, useQuery as v } from "@apollo/client/react";
12
- import { AlertDialog as y, AlertDialogAction as b, AlertDialogCancel as x, AlertDialogContent as S, AlertDialogDescription as C, AlertDialogFooter as w, AlertDialogHeader as T, AlertDialogTitle as E, CTAOverflowMenu as D, GlassCard as O, IllustratedEmptyState as k, NextSteps as A, PagePurpose as j } from "@burdenoff/fe-libs/ui";
13
- import { jsx as M, jsxs as N } from "react/jsx-runtime";
7
+ import { useMemo as c, useState as l } from "react";
8
+ import { CalendarCheck as u, Compass as d, Hotel as f, Plus as p, Ticket as m, XCircle as h } from "lucide-react";
9
+ import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
+ import { useNavigate as _ } from "react-router-dom";
11
+ import { useMutation as v, useQuery as y } from "@apollo/client/react";
12
+ import { AlertDialog as b, AlertDialogAction as x, AlertDialogCancel as S, AlertDialogContent as C, AlertDialogDescription as w, AlertDialogFooter as T, AlertDialogHeader as E, AlertDialogTitle as D, CTAOverflowMenu as O, GlassCard as k, IllustratedEmptyState as A, NextSteps as j, PagePurpose as M } from "@burdenoff/fe-libs/ui";
13
+ import { jsx as N, jsxs as P } from "react/jsx-runtime";
14
14
  //#region src/ggnomad/pages/BookingsPage.tsx
15
- function P() {
16
- let { t: P } = h(), F = g(), [I, L] = c(null), { data: R, refetch: z } = v(r, { variables: {
15
+ function F() {
16
+ let { t: F } = g(), I = _(), [L, R] = l(null), { data: z, refetch: B } = y(r, { variables: {
17
17
  first: 20,
18
18
  skip: 0
19
- } }), { data: B } = v(n), [V, { loading: H }] = _(t), [U, { loading: W }] = _(e), G = R?.ggnomadBookings ?? [], K = () => {
20
- let e = B?.ggnomadProperties?.[0];
19
+ } }), { data: V } = y(n), [H, { loading: U }] = v(t), [W, { loading: G }] = v(e), K = c(() => z?.ggnomadBookings ?? [], [z?.ggnomadBookings]), q = c(() => {
20
+ let e = {
21
+ CONFIRMED: 0,
22
+ CHECKED_IN: 0,
23
+ COMPLETED: 0,
24
+ CANCELLED: 0
25
+ };
26
+ return K.forEach((t) => {
27
+ let n = t.status?.toUpperCase();
28
+ n && n in e && (e[n] += 1);
29
+ }), e;
30
+ }, [K]), J = () => {
31
+ let e = V?.ggnomadProperties?.[0];
21
32
  if (e) return {
22
33
  type: "PROPERTY",
23
34
  listing: {
@@ -27,34 +38,34 @@ function P() {
27
38
  currency: e.currency
28
39
  }
29
40
  };
30
- let t = B?.ggnomadTours?.[0];
41
+ let t = V?.ggnomadTours?.[0];
31
42
  if (t) return {
32
43
  type: "TOUR",
33
44
  listing: t
34
45
  };
35
- let n = B?.ggnomadEvents?.[0];
46
+ let n = V?.ggnomadEvents?.[0];
36
47
  if (n) return {
37
48
  type: "EVENT",
38
49
  listing: n
39
50
  };
40
- let r = B?.ggnomadActivities?.[0];
51
+ let r = V?.ggnomadActivities?.[0];
41
52
  return r ? {
42
53
  type: "ACTIVITY",
43
54
  listing: r
44
55
  } : null;
45
- }, q = async () => {
46
- let e = K();
56
+ }, Y = async () => {
57
+ let e = J();
47
58
  if (!e) {
48
- s.warning(P("ggnomad.bookings.toast.noListingsTitle", "No listings available to book yet"), P("ggnomad.bookings.toast.noListingsBody", "Add a property, tour, event or activity first, then create a booking against it."));
59
+ s.warning(F("ggnomad.bookings.toast.noListingsTitle", "No listings available to book yet"), F("ggnomad.bookings.toast.noListingsBody", "Add a property, tour, event or activity first, then create a booking against it."));
49
60
  return;
50
61
  }
51
62
  let { type: t, listing: n } = e;
52
63
  try {
53
- let e = await V({ variables: { input: {
64
+ let e = await H({ variables: { input: {
54
65
  bookingType: t,
55
66
  itemId: n.id,
56
67
  itemName: n.name,
57
- guestName: P("ggnomad.bookings.demo.guestName", "Demo Guest"),
68
+ guestName: F("ggnomad.bookings.demo.guestName", "Demo Guest"),
58
69
  guestEmail: "demo@burdenoff.com",
59
70
  guests: 2,
60
71
  totalPrice: Number(n.price ?? 0),
@@ -62,132 +73,176 @@ function P() {
62
73
  paymentMethod: "card"
63
74
  } } });
64
75
  if (e.error) {
65
- s.error(P("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
76
+ s.error(F("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
66
77
  return;
67
78
  }
68
79
  let r = e.data?.createGgnomadBooking;
69
- s.success(P("ggnomad.bookings.toast.createSuccessTitle", "Booking confirmed"), r ? P("ggnomad.bookings.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : n.name), await z();
80
+ s.success(F("ggnomad.bookings.toast.createSuccessTitle", "Booking confirmed"), r ? F("ggnomad.bookings.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : n.name), await B();
70
81
  } catch (e) {
71
- s.error(P("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
82
+ s.error(F("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
72
83
  }
73
- }, J = async () => {
74
- if (I) try {
75
- let e = await U({ variables: {
76
- id: I.id,
77
- reason: P("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
84
+ }, X = async () => {
85
+ if (L) try {
86
+ let e = await W({ variables: {
87
+ id: L.id,
88
+ reason: F("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
78
89
  } });
79
90
  if (e.error) {
80
- s.error(P("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
91
+ s.error(F("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
81
92
  return;
82
93
  }
83
- s.success(P("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), I.referenceCode), await z();
94
+ s.success(F("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), L.referenceCode), await B();
84
95
  } catch (e) {
85
- s.error(P("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
96
+ s.error(F("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
86
97
  } finally {
87
- L(null);
98
+ R(null);
88
99
  }
89
100
  };
90
- return /* @__PURE__ */ N(i, {
91
- title: P("ggnomad.bookings.title", "Bookings"),
92
- titleAccent: P("ggnomad.bookings.title.accent", "Bookings"),
93
- subtitle: P("ggnomad.bookings.subtitle", "End-to-end booking operations with real API mutations"),
94
- actions: /* @__PURE__ */ M(D, {
101
+ return /* @__PURE__ */ P(i, {
102
+ title: F("ggnomad.bookings.title", "Bookings"),
103
+ titleAccent: F("ggnomad.bookings.title.accent", "Bookings"),
104
+ subtitle: F("ggnomad.bookings.subtitle", "End-to-end booking operations with real API mutations"),
105
+ actions: /* @__PURE__ */ N(O, {
95
106
  primary: {
96
- label: P("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
97
- onSelect: () => void q(),
98
- disabled: H,
99
- icon: /* @__PURE__ */ M(f, {})
107
+ label: F("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
108
+ onSelect: () => void Y(),
109
+ disabled: U,
110
+ icon: /* @__PURE__ */ N(p, {})
100
111
  },
101
112
  actions: []
102
113
  }),
103
114
  children: [
104
- /* @__PURE__ */ M(j, {
105
- icon: /* @__PURE__ */ M(l, { className: "size-4" }),
106
- children: P("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.")
115
+ /* @__PURE__ */ N(M, {
116
+ icon: /* @__PURE__ */ N(u, { className: "size-4" }),
117
+ children: F("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.")
107
118
  }),
108
- /* @__PURE__ */ M(A, {
119
+ /* @__PURE__ */ N(j, {
109
120
  storageKey: "ggnomad-bookings",
110
121
  steps: [
111
122
  {
112
123
  id: "book-stay",
113
- label: P("ggnomad.bookings.next.stay", "Book a place to stay"),
114
- description: P("ggnomad.bookings.next.stayDesc", "Browse hospitality listings and reserve a property."),
115
- icon: /* @__PURE__ */ M(d, { className: "size-4" }),
116
- onClick: () => F("../hospitality")
124
+ label: F("ggnomad.bookings.next.stay", "Book a place to stay"),
125
+ description: F("ggnomad.bookings.next.stayDesc", "Browse hospitality listings and reserve a property."),
126
+ icon: /* @__PURE__ */ N(f, { className: "size-4" }),
127
+ onClick: () => I("../hospitality")
117
128
  },
118
129
  {
119
130
  id: "book-tour",
120
- label: P("ggnomad.bookings.next.tour", "Add a tour or activity"),
121
- description: P("ggnomad.bookings.next.tourDesc", "Pick a curated experience from Tourism."),
122
- icon: /* @__PURE__ */ M(u, { className: "size-4" }),
123
- onClick: () => F("../tourism")
131
+ label: F("ggnomad.bookings.next.tour", "Add a tour or activity"),
132
+ description: F("ggnomad.bookings.next.tourDesc", "Pick a curated experience from Tourism."),
133
+ icon: /* @__PURE__ */ N(d, { className: "size-4" }),
134
+ onClick: () => I("../tourism")
124
135
  },
125
136
  {
126
137
  id: "book-event",
127
- label: P("ggnomad.bookings.next.event", "Grab event tickets"),
128
- description: P("ggnomad.bookings.next.eventDesc", "Find live events to round out the itinerary."),
129
- icon: /* @__PURE__ */ M(p, { className: "size-4" }),
130
- onClick: () => F("../entertainment")
138
+ label: F("ggnomad.bookings.next.event", "Grab event tickets"),
139
+ description: F("ggnomad.bookings.next.eventDesc", "Find live events to round out the itinerary."),
140
+ icon: /* @__PURE__ */ N(m, { className: "size-4" }),
141
+ onClick: () => I("../entertainment")
131
142
  }
132
143
  ]
133
144
  }),
134
- G.length === 0 ? /* @__PURE__ */ M(k, {
145
+ K.length > 0 && /* @__PURE__ */ N("div", {
146
+ className: "grid gap-3 sm:grid-cols-4",
147
+ children: [
148
+ {
149
+ status: "CONFIRMED",
150
+ label: F("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
151
+ icon: u
152
+ },
153
+ {
154
+ status: "CHECKED_IN",
155
+ label: F("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
156
+ icon: f
157
+ },
158
+ {
159
+ status: "COMPLETED",
160
+ label: F("ggnomad.bookings.pipeline.completed", "Completed"),
161
+ icon: m
162
+ },
163
+ {
164
+ status: "CANCELLED",
165
+ label: F("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
166
+ icon: h
167
+ }
168
+ ].map((e) => {
169
+ let t = e.icon, n = q[e.status];
170
+ return /* @__PURE__ */ P("div", {
171
+ className: "flex flex-col items-center gap-2 rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-center",
172
+ children: [
173
+ /* @__PURE__ */ N("div", {
174
+ className: "flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary",
175
+ children: /* @__PURE__ */ N(t, { className: "size-4" })
176
+ }),
177
+ /* @__PURE__ */ N("p", {
178
+ className: "text-xs font-medium text-text-secondary",
179
+ children: e.label
180
+ }),
181
+ /* @__PURE__ */ N("p", {
182
+ className: "text-lg font-semibold tabular-nums text-text-primary",
183
+ children: n
184
+ })
185
+ ]
186
+ }, e.status);
187
+ })
188
+ }),
189
+ K.length === 0 ? /* @__PURE__ */ N(A, {
135
190
  illustration: "empty-data",
136
- title: P("ggnomad.bookings.emptyTitle", "No bookings yet"),
137
- description: P("ggnomad.bookings.emptyDescription", "Create your first booking to see it tracked here in real time.")
138
- }) : /* @__PURE__ */ N(O, {
191
+ title: F("ggnomad.bookings.emptyTitle", "No bookings yet"),
192
+ description: F("ggnomad.bookings.emptyDescription", "Create your first booking to see it tracked here in real time.")
193
+ }) : /* @__PURE__ */ P(k, {
139
194
  treatment: "glass",
140
195
  glow: !0,
141
196
  className: "border-border-seam p-5",
142
- children: [/* @__PURE__ */ M("h2", {
197
+ children: [/* @__PURE__ */ N("h2", {
143
198
  className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
144
- children: P("ggnomad.bookings.activeBookings", "Your bookings")
145
- }), /* @__PURE__ */ M("div", {
199
+ children: F("ggnomad.bookings.activeBookings", "Your bookings")
200
+ }), /* @__PURE__ */ N("div", {
146
201
  className: "grid gap-3 sm:grid-cols-2",
147
- children: G.map((e) => /* @__PURE__ */ M(o, {
202
+ children: K.map((e) => /* @__PURE__ */ N(o, {
148
203
  title: e.referenceCode,
149
204
  meta: e.itemName,
150
205
  price: `${e.currency} ${Number(e.totalPrice).toFixed(2)}`,
151
- badge: /* @__PURE__ */ M(a, {
206
+ badge: /* @__PURE__ */ N(a, {
152
207
  status: e.status,
153
208
  live: e.status !== "CANCELLED"
154
209
  }),
155
- footer: e.status === "CANCELLED" ? null : /* @__PURE__ */ M(D, {
210
+ footer: e.status === "CANCELLED" ? null : /* @__PURE__ */ N(O, {
156
211
  align: "end",
157
212
  actions: [{
158
- label: P("ggnomad.bookings.actions.cancel", "Cancel booking"),
159
- onSelect: () => L({
213
+ label: F("ggnomad.bookings.actions.cancel", "Cancel booking"),
214
+ onSelect: () => R({
160
215
  id: e.id,
161
216
  referenceCode: e.referenceCode
162
217
  }),
163
218
  intent: "destructive",
164
- icon: /* @__PURE__ */ M(m, {})
219
+ icon: /* @__PURE__ */ N(h, {})
165
220
  }]
166
221
  })
167
222
  }, e.id))
168
223
  })]
169
224
  }),
170
- /* @__PURE__ */ M(y, {
171
- open: I !== null,
225
+ /* @__PURE__ */ N(b, {
226
+ open: L !== null,
172
227
  onOpenChange: (e) => {
173
- e || L(null);
228
+ e || R(null);
174
229
  },
175
- children: /* @__PURE__ */ N(S, { children: [/* @__PURE__ */ N(T, { children: [/* @__PURE__ */ M(E, { children: P("ggnomad.bookings.cancelDialog.title", "Cancel this booking?") }), /* @__PURE__ */ M(C, { children: P("ggnomad.bookings.cancelDialog.body", "This releases reservation {{ref}} and cannot be undone.").replace("{{ref}}", I?.referenceCode ?? "") })] }), /* @__PURE__ */ N(w, { children: [/* @__PURE__ */ M(x, {
176
- disabled: W,
177
- children: P("ggnomad.bookings.cancelDialog.keep", "Keep booking")
178
- }), /* @__PURE__ */ M(b, {
230
+ children: /* @__PURE__ */ P(C, { children: [/* @__PURE__ */ P(E, { children: [/* @__PURE__ */ N(D, { children: F("ggnomad.bookings.cancelDialog.title", "Cancel this booking?") }), /* @__PURE__ */ N(w, { children: F("ggnomad.bookings.cancelDialog.body", "This releases reservation {{ref}} and cannot be undone.").replace("{{ref}}", L?.referenceCode ?? "") })] }), /* @__PURE__ */ P(T, { children: [/* @__PURE__ */ N(S, {
231
+ disabled: G,
232
+ children: F("ggnomad.bookings.cancelDialog.keep", "Keep booking")
233
+ }), /* @__PURE__ */ N(x, {
179
234
  className: "bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover",
180
- disabled: W,
235
+ disabled: G,
181
236
  onClick: (e) => {
182
- e.preventDefault(), J();
237
+ e.preventDefault(), X();
183
238
  },
184
- children: W ? P("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : P("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
239
+ children: G ? F("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : F("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
185
240
  })] })] })
186
241
  })
187
242
  ]
188
243
  });
189
244
  }
190
245
  //#endregion
191
- export { P as BookingsPage };
246
+ export { F as BookingsPage };
192
247
 
193
248
  //# sourceMappingURL=BookingsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BookingsPage.js","names":[],"sources":["../../../src/ggnomad/pages/BookingsPage.tsx"],"sourcesContent":["import { 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, Ticket, XCircle } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\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, StatusBadge } 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\nexport function BookingsPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const [cancelTarget, setCancelTarget] = useState<CancelTarget | 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 { first: number; skip: number }\n >,\n { variables: { first: 20, skip: 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 = data?.ggnomadBookings ?? [];\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 {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.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 </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 { CalendarCheck, Compass, Hotel, Plus, Ticket, XCircle } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\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, StatusBadge } 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\nexport function BookingsPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const [cancelTarget, setCancelTarget] = useState<CancelTarget | 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 { first: number; skip: number }\n >,\n { variables: { first: 20, skip: 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 {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.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 </PageShell>\n );\n}\n"],"mappings":"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"}