@burdenoff/microfe-ggnomad 2026.719.4 → 2026.725.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.
@@ -0,0 +1,13 @@
1
+ interface RedeemTicketPanelProps {
2
+ onRedeemed?: () => void;
3
+ }
4
+ /**
5
+ * Provider-facing check-in panel (BOFF-4362). A venue operator pastes/scans a
6
+ * guest's QR token here to validate + redeem it — the same
7
+ * `redeemGgnomadTicket` mutation a dedicated scanner device would call.
8
+ * Redemption is server-side validated (signature + live ticket status), so
9
+ * this panel never has to trust anything about the token itself.
10
+ */
11
+ export declare function RedeemTicketPanel({ onRedeemed }: RedeemTicketPanelProps): import("react").JSX.Element;
12
+ export {};
13
+ //# sourceMappingURL=RedeemTicketPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedeemTicketPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/RedeemTicketPanel.tsx"],"names":[],"mappings":"AAmBA,UAAU,sBAAsB;IAC9B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,EAAE,sBAAsB,+BA8EvE"}
@@ -0,0 +1,69 @@
1
+ import { REDEEM_GGNOMAD_TICKET_MUTATION as e } from "../graphql.js";
2
+ import { notify as t } from "../utils/notify.js";
3
+ import { useState as n } from "react";
4
+ import { ScanLine as r } from "lucide-react";
5
+ import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
+ import { useMutation as a } from "@apollo/client/react";
7
+ import { GlassCard as o } from "@burdenoff/fe-libs/ui";
8
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
9
+ //#region src/ggnomad/components/RedeemTicketPanel.tsx
10
+ function l({ onRedeemed: l }) {
11
+ let { t: u } = i(), [d, f] = n(""), [p, { loading: m }] = a(e), h = async () => {
12
+ let e = d.trim();
13
+ if (e) try {
14
+ let n = await p({ variables: { token: e } });
15
+ if (n.error) {
16
+ t.error(u("ggnomad.redeem.errorTitle", "Could not redeem ticket"), n.error.message);
17
+ return;
18
+ }
19
+ let r = n.data?.redeemGgnomadTicket;
20
+ t.success(u("ggnomad.redeem.successTitle", "Guest checked in"), r ? u("ggnomad.redeem.successBody", "Ticket {{code}} redeemed.").replace("{{code}}", r.ticketCode) : void 0), f(""), l?.();
21
+ } catch (e) {
22
+ t.error(u("ggnomad.redeem.errorTitle", "Could not redeem ticket"), e instanceof Error ? e.message : String(e));
23
+ }
24
+ };
25
+ return /* @__PURE__ */ c(o, {
26
+ treatment: "glass",
27
+ className: "border-border-seam p-5",
28
+ children: [
29
+ /* @__PURE__ */ c("div", {
30
+ className: "mb-3 flex items-center gap-2",
31
+ children: [/* @__PURE__ */ s(r, {
32
+ className: "size-4 text-text-secondary",
33
+ "aria-hidden": "true"
34
+ }), /* @__PURE__ */ s("h2", {
35
+ className: "text-lg font-medium tracking-tight text-text-primary",
36
+ children: u("ggnomad.redeem.title", "Check in a guest")
37
+ })]
38
+ }),
39
+ /* @__PURE__ */ s("p", {
40
+ className: "mb-4 text-sm text-text-secondary",
41
+ children: u("ggnomad.redeem.purpose", "Paste the token from a scanned QR ticket to validate and check the guest in. Revoked or already-redeemed tickets are rejected automatically.")
42
+ }),
43
+ /* @__PURE__ */ c("form", {
44
+ className: "flex flex-col gap-2 sm:flex-row",
45
+ onSubmit: (e) => {
46
+ e.preventDefault(), h();
47
+ },
48
+ children: [/* @__PURE__ */ s("input", {
49
+ type: "text",
50
+ value: d,
51
+ onChange: (e) => f(e.target.value),
52
+ disabled: m,
53
+ placeholder: u("ggnomad.redeem.placeholder", "Scanned ticket token"),
54
+ "aria-label": u("ggnomad.redeem.inputLabel", "Ticket token"),
55
+ className: "min-w-0 flex-1 rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm font-mono text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50"
56
+ }), /* @__PURE__ */ s("button", {
57
+ type: "submit",
58
+ disabled: m || !d.trim(),
59
+ className: "shrink-0 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50",
60
+ children: m ? u("ggnomad.redeem.checking", "Checking in…") : u("ggnomad.redeem.action", "Check in")
61
+ })]
62
+ })
63
+ ]
64
+ });
65
+ }
66
+ //#endregion
67
+ export { l as RedeemTicketPanel };
68
+
69
+ //# sourceMappingURL=RedeemTicketPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedeemTicketPanel.js","names":[],"sources":["../../../src/ggnomad/components/RedeemTicketPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ScanLine } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { REDEEM_GGNOMAD_TICKET_MUTATION } from '../graphql';\nimport { notify } from '../utils/notify';\n\ninterface RedeemedTicket {\n id: string;\n bookingId: string;\n ticketCode: string;\n status: string;\n redeemedAt: string | null;\n redeemedBy: string | null;\n}\n\ninterface RedeemTicketPanelProps {\n onRedeemed?: () => void;\n}\n\n/**\n * Provider-facing check-in panel (BOFF-4362). A venue operator pastes/scans a\n * guest's QR token here to validate + redeem it — the same\n * `redeemGgnomadTicket` mutation a dedicated scanner device would call.\n * Redemption is server-side validated (signature + live ticket status), so\n * this panel never has to trust anything about the token itself.\n */\nexport function RedeemTicketPanel({ onRedeemed }: RedeemTicketPanelProps) {\n const { t } = useI18n();\n const [token, setToken] = useState('');\n\n const [redeemTicket, { loading }] = useMutation(\n REDEEM_GGNOMAD_TICKET_MUTATION as TypedDocumentNode<\n { redeemGgnomadTicket: RedeemedTicket },\n { token: string }\n >\n );\n\n const handleRedeem = async () => {\n const trimmed = token.trim();\n if (!trimmed) return;\n\n try {\n const result = await redeemTicket({ variables: { token: trimmed } });\n if (result.error) {\n notify.error(t('ggnomad.redeem.errorTitle', 'Could not redeem ticket'), result.error.message);\n return;\n }\n const redeemed = result.data?.redeemGgnomadTicket;\n notify.success(\n t('ggnomad.redeem.successTitle', 'Guest checked in'),\n redeemed\n ? t('ggnomad.redeem.successBody', 'Ticket {{code}} redeemed.').replace('{{code}}', redeemed.ticketCode)\n : undefined\n );\n setToken('');\n onRedeemed?.();\n } catch (err) {\n notify.error(\n t('ggnomad.redeem.errorTitle', 'Could not redeem ticket'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <GlassCard treatment=\"glass\" className=\"border-border-seam p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <ScanLine className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.redeem.title', 'Check in a guest')}\n </h2>\n </div>\n <p className=\"mb-4 text-sm text-text-secondary\">\n {t(\n 'ggnomad.redeem.purpose',\n 'Paste the token from a scanned QR ticket to validate and check the guest in. Revoked or already-redeemed tickets are rejected automatically.'\n )}\n </p>\n <form\n className=\"flex flex-col gap-2 sm:flex-row\"\n onSubmit={(event) => {\n event.preventDefault();\n void handleRedeem();\n }}\n >\n <input\n type=\"text\"\n value={token}\n onChange={(e) => setToken(e.target.value)}\n disabled={loading}\n placeholder={t('ggnomad.redeem.placeholder', 'Scanned ticket token')}\n aria-label={t('ggnomad.redeem.inputLabel', 'Ticket token')}\n className=\"min-w-0 flex-1 rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm font-mono text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n />\n <button\n type=\"submit\"\n disabled={loading || !token.trim()}\n className=\"shrink-0 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {loading ? t('ggnomad.redeem.checking', 'Checking in…') : t('ggnomad.redeem.action', 'Check in')}\n </button>\n </form>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;AA8BA,SAAgB,EAAkB,EAAE,iBAAsC;CACxE,IAAM,EAAE,SAAM,EAAQ,GAChB,CAAC,GAAO,KAAY,EAAS,EAAE,GAE/B,CAAC,GAAc,EAAE,gBAAa,EAClC,CAIF,GAEM,IAAe,YAAY;EAC/B,IAAM,IAAU,EAAM,KAAK;EACtB,OAEL,IAAI;GACF,IAAM,IAAS,MAAM,EAAa,EAAE,WAAW,EAAE,OAAO,EAAQ,EAAE,CAAC;GACnE,IAAI,EAAO,OAAO;IAChB,EAAO,MAAM,EAAE,6BAA6B,yBAAyB,GAAG,EAAO,MAAM,OAAO;IAC5F;GACF;GACA,IAAM,IAAW,EAAO,MAAM;GAQ9B,AAPA,EAAO,QACL,EAAE,+BAA+B,kBAAkB,GACnD,IACI,EAAE,8BAA8B,2BAA2B,EAAE,QAAQ,YAAY,EAAS,UAAU,IACpG,KAAA,CACN,GACA,EAAS,EAAE,GACX,IAAa;EACf,SAAS,GAAK;GACZ,EAAO,MACL,EAAE,6BAA6B,yBAAyB,GACxD,aAAe,QAAQ,EAAI,UAAU,OAAO,CAAG,CACjD;EACF;CACF;CAEA,OACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAU,WAAU;KAA6B,eAAY;IAAQ,CAAA,GACrE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAE,wBAAwB,kBAAkB;IAC3C,CAAA,CACD;;GACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,0BACA,8IACF;GACC,CAAA;GACH,kBAAC,QAAD;IACE,WAAU;IACV,WAAW,MAAU;KAEnB,AADA,EAAM,eAAe,GACrB,EAAkB;IACpB;cALF,CAOE,kBAAC,SAAD;KACE,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAS,EAAE,OAAO,KAAK;KACxC,UAAU;KACV,aAAa,EAAE,8BAA8B,sBAAsB;KACnE,cAAY,EAAE,6BAA6B,cAAc;KACzD,WAAU;IACX,CAAA,GACD,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,KAAW,CAAC,EAAM,KAAK;KACjC,WAAU;eAET,IAAU,EAAE,2BAA2B,cAAc,IAAI,EAAE,yBAAyB,UAAU;IACzF,CAAA,CACJ;;EACG;;AAEf"}
@@ -0,0 +1,28 @@
1
+ export interface GgnomadTicket {
2
+ id: string;
3
+ bookingId: string;
4
+ ticketCode: string;
5
+ qrCodeDataUrl: string;
6
+ status: 'ISSUED' | 'REDEEMED' | 'REVOKED';
7
+ issuedAt: string;
8
+ redeemedAt: string | null;
9
+ redeemedBy: string | null;
10
+ revokedAt: string | null;
11
+ revokedReason: string | null;
12
+ }
13
+ interface TicketDialogProps {
14
+ open: boolean;
15
+ onOpenChange: (open: boolean) => void;
16
+ bookingId: string;
17
+ referenceCode: string;
18
+ bookingStatus: string;
19
+ }
20
+ /**
21
+ * Traveler-facing mobile ticket view (BOFF-4362). Shows every QR ticket
22
+ * issued for a booking — each ticket's QR code is rendered server-side from
23
+ * its signed, revocable token (see wspace-ggnomad-svc `qrCodeDataUrl`), so
24
+ * what's displayed here always reflects the ticket's live redemption state.
25
+ */
26
+ export declare function TicketDialog({ open, onOpenChange, bookingId, referenceCode, bookingStatus, }: TicketDialogProps): import("react").JSX.Element;
27
+ export {};
28
+ //# sourceMappingURL=TicketDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TicketDialog.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/TicketDialog.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAKD,UAAU,iBAAiB;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AASD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,aAAa,EACb,aAAa,GACd,EAAE,iBAAiB,+BA4InB"}
@@ -0,0 +1,126 @@
1
+ import { GGNOMAD_TICKETS_QUERY as e, ISSUE_GGNOMAD_TICKET_MUTATION as t } from "../graphql.js";
2
+ import { StatusBadge as n } from "./StatusBadge.js";
3
+ import { notify as r } from "../utils/notify.js";
4
+ import { QrCode as i, Ticket as a } from "lucide-react";
5
+ import { useI18n as o } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
+ import { useMutation as s, useQuery as c } from "@apollo/client/react";
7
+ import { AlertDialog as l, AlertDialogCancel as u, AlertDialogContent as d, AlertDialogDescription as f, AlertDialogFooter as p, AlertDialogHeader as m, AlertDialogTitle as h } from "@burdenoff/fe-libs/ui";
8
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
9
+ //#region src/ggnomad/components/TicketDialog.tsx
10
+ var v = new Set([
11
+ "CONFIRMED",
12
+ "CHECKED_IN",
13
+ "COMPLETED"
14
+ ]);
15
+ function y(e) {
16
+ if (!e) return "";
17
+ let t = new Date(e);
18
+ return Number.isNaN(t.getTime()) ? e : t.toLocaleString();
19
+ }
20
+ function b({ open: b, onOpenChange: x, bookingId: S, referenceCode: C, bookingStatus: w }) {
21
+ let { t: T } = o(), { data: E, loading: D, refetch: O } = c(e, {
22
+ variables: { bookingId: S },
23
+ skip: !b || !S,
24
+ fetchPolicy: "network-only"
25
+ }), [k, { loading: A }] = s(t), j = E?.ggnomadTickets ?? [], M = v.has(w.toUpperCase()), N = async () => {
26
+ try {
27
+ let e = await k({ variables: { bookingId: S } });
28
+ if (e.error) {
29
+ r.error(T("ggnomad.ticket.issueErrorTitle", "Could not issue ticket"), e.error.message);
30
+ return;
31
+ }
32
+ r.success(T("ggnomad.ticket.issueSuccessTitle", "Ticket issued"), T("ggnomad.ticket.issueSuccessBody", "A new QR ticket is ready to show at check-in.")), await O();
33
+ } catch (e) {
34
+ r.error(T("ggnomad.ticket.issueErrorTitle", "Could not issue ticket"), e instanceof Error ? e.message : String(e));
35
+ }
36
+ };
37
+ return /* @__PURE__ */ g(l, {
38
+ open: b,
39
+ onOpenChange: x,
40
+ children: /* @__PURE__ */ _(d, {
41
+ className: "max-w-md",
42
+ children: [
43
+ /* @__PURE__ */ _(m, { children: [/* @__PURE__ */ _(h, {
44
+ className: "flex items-center gap-2",
45
+ children: [/* @__PURE__ */ g(a, { className: "size-4" }), T("ggnomad.ticket.title", "Mobile ticket")]
46
+ }), /* @__PURE__ */ g(f, { children: T("ggnomad.ticket.description", "Show this QR code at check-in for {{ref}}.").replace("{{ref}}", C) })] }),
47
+ /* @__PURE__ */ g("div", {
48
+ className: "max-h-[65vh] space-y-4 overflow-y-auto",
49
+ children: D ? /* @__PURE__ */ g("p", {
50
+ className: "py-6 text-center text-sm text-text-secondary",
51
+ children: T("ggnomad.ticket.loading", "Loading your ticket…")
52
+ }) : j.length === 0 ? /* @__PURE__ */ _("div", {
53
+ className: "flex flex-col items-center gap-3 rounded-lg border border-dashed border-border-seam py-8 text-center",
54
+ children: [
55
+ /* @__PURE__ */ g(i, {
56
+ className: "size-8 text-text-secondary",
57
+ "aria-hidden": "true"
58
+ }),
59
+ /* @__PURE__ */ g("p", {
60
+ className: "text-sm text-text-secondary",
61
+ children: M ? T("ggnomad.ticket.noneYet", "No QR ticket has been issued for this booking yet.") : T("ggnomad.ticket.notConfirmed", "A QR ticket will be issued automatically once this booking is confirmed.")
62
+ }),
63
+ M ? /* @__PURE__ */ g("button", {
64
+ type: "button",
65
+ onClick: () => void N(),
66
+ disabled: A,
67
+ className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50",
68
+ children: A ? T("ggnomad.ticket.issuing", "Issuing…") : T("ggnomad.ticket.issueAction", "Issue QR ticket")
69
+ }) : null
70
+ ]
71
+ }) : j.map((e) => /* @__PURE__ */ _("div", {
72
+ className: "flex flex-col items-center gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-4",
73
+ children: [
74
+ /* @__PURE__ */ _("div", {
75
+ className: "flex w-full items-center justify-between",
76
+ children: [/* @__PURE__ */ g("span", {
77
+ className: "font-mono text-xs text-text-secondary",
78
+ children: e.ticketCode
79
+ }), /* @__PURE__ */ g(n, {
80
+ status: e.status,
81
+ live: e.status === "ISSUED"
82
+ })]
83
+ }),
84
+ e.status === "ISSUED" ? /* @__PURE__ */ g("img", {
85
+ src: e.qrCodeDataUrl,
86
+ alt: T("ggnomad.ticket.qrAlt", "QR code for ticket {{code}}").replace("{{code}}", e.ticketCode),
87
+ className: "size-48 rounded-md border border-border-seam bg-white p-2",
88
+ width: 192,
89
+ height: 192
90
+ }) : /* @__PURE__ */ g("div", {
91
+ className: "flex size-48 items-center justify-center rounded-md border border-dashed border-border-seam bg-bg-sunken text-center text-xs text-text-secondary",
92
+ children: e.status === "REDEEMED" ? T("ggnomad.ticket.alreadyRedeemed", "Already redeemed — QR retired") : T("ggnomad.ticket.revokedNotice", "Revoked — no longer valid")
93
+ }),
94
+ /* @__PURE__ */ _("div", {
95
+ className: "w-full space-y-1 text-xs text-text-secondary",
96
+ children: [
97
+ /* @__PURE__ */ _("p", { children: [
98
+ T("ggnomad.ticket.issuedAt", "Issued"),
99
+ " ",
100
+ y(e.issuedAt)
101
+ ] }),
102
+ e.redeemedAt ? /* @__PURE__ */ _("p", { children: [
103
+ T("ggnomad.ticket.redeemedAt", "Redeemed"),
104
+ " ",
105
+ y(e.redeemedAt)
106
+ ] }) : null,
107
+ e.revokedAt ? /* @__PURE__ */ _("p", { children: [
108
+ T("ggnomad.ticket.revokedAt", "Revoked"),
109
+ " ",
110
+ y(e.revokedAt),
111
+ e.revokedReason ? ` — ${e.revokedReason}` : ""
112
+ ] }) : null
113
+ ]
114
+ })
115
+ ]
116
+ }, e.id))
117
+ }),
118
+ /* @__PURE__ */ g(p, { children: /* @__PURE__ */ g(u, { children: T("ggnomad.ticket.close", "Close") }) })
119
+ ]
120
+ })
121
+ });
122
+ }
123
+ //#endregion
124
+ export { b as TicketDialog };
125
+
126
+ //# sourceMappingURL=TicketDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TicketDialog.js","names":[],"sources":["../../../src/ggnomad/components/TicketDialog.tsx"],"sourcesContent":["import { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { QrCode, Ticket as TicketIcon } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_TICKETS_QUERY, ISSUE_GGNOMAD_TICKET_MUTATION } from '../graphql';\nimport { StatusBadge } from './StatusBadge';\nimport { notify } from '../utils/notify';\n\nexport interface GgnomadTicket {\n id: string;\n bookingId: string;\n ticketCode: string;\n qrCodeDataUrl: string;\n status: 'ISSUED' | 'REDEEMED' | 'REVOKED';\n issuedAt: string;\n redeemedAt: string | null;\n redeemedBy: string | null;\n revokedAt: string | null;\n revokedReason: string | null;\n}\n\n/** Bookings reach CONFIRMED (payment verified) before a ticket can ever exist. */\nconst TICKET_ELIGIBLE_STATUSES = new Set(['CONFIRMED', 'CHECKED_IN', 'COMPLETED']);\n\ninterface TicketDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n bookingId: string;\n referenceCode: string;\n bookingStatus: string;\n}\n\nfunction formatDateTime(value: string | null): string {\n if (!value) return '';\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n return date.toLocaleString();\n}\n\n/**\n * Traveler-facing mobile ticket view (BOFF-4362). Shows every QR ticket\n * issued for a booking — each ticket's QR code is rendered server-side from\n * its signed, revocable token (see wspace-ggnomad-svc `qrCodeDataUrl`), so\n * what's displayed here always reflects the ticket's live redemption state.\n */\nexport function TicketDialog({\n open,\n onOpenChange,\n bookingId,\n referenceCode,\n bookingStatus,\n}: TicketDialogProps) {\n const { t } = useI18n();\n\n const { data, loading, refetch } = useQuery(\n GGNOMAD_TICKETS_QUERY as TypedDocumentNode<{ ggnomadTickets: GgnomadTicket[] }, { bookingId: string }>,\n { variables: { bookingId }, skip: !open || !bookingId, fetchPolicy: 'network-only' }\n );\n\n const [issueTicket, { loading: issuing }] = useMutation(\n ISSUE_GGNOMAD_TICKET_MUTATION as TypedDocumentNode<\n { issueGgnomadTicket: GgnomadTicket },\n { bookingId: string }\n >\n );\n\n const tickets = data?.ggnomadTickets ?? [];\n const canIssue = TICKET_ELIGIBLE_STATUSES.has(bookingStatus.toUpperCase());\n\n const handleIssue = async () => {\n try {\n const result = await issueTicket({ variables: { bookingId } });\n if (result.error) {\n notify.error(t('ggnomad.ticket.issueErrorTitle', 'Could not issue ticket'), result.error.message);\n return;\n }\n notify.success(\n t('ggnomad.ticket.issueSuccessTitle', 'Ticket issued'),\n t('ggnomad.ticket.issueSuccessBody', 'A new QR ticket is ready to show at check-in.')\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.ticket.issueErrorTitle', 'Could not issue ticket'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-md\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <TicketIcon className=\"size-4\" />\n {t('ggnomad.ticket.title', 'Mobile ticket')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t('ggnomad.ticket.description', 'Show this QR code at check-in for {{ref}}.').replace(\n '{{ref}}',\n referenceCode\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"max-h-[65vh] space-y-4 overflow-y-auto\">\n {loading ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t('ggnomad.ticket.loading', 'Loading your ticket…')}\n </p>\n ) : tickets.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 rounded-lg border border-dashed border-border-seam py-8 text-center\">\n <QrCode className=\"size-8 text-text-secondary\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n {canIssue\n ? t('ggnomad.ticket.noneYet', 'No QR ticket has been issued for this booking yet.')\n : t(\n 'ggnomad.ticket.notConfirmed',\n 'A QR ticket will be issued automatically once this booking is confirmed.'\n )}\n </p>\n {canIssue ? (\n <button\n type=\"button\"\n onClick={() => void handleIssue()}\n disabled={issuing}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {issuing\n ? t('ggnomad.ticket.issuing', 'Issuing…')\n : t('ggnomad.ticket.issueAction', 'Issue QR ticket')}\n </button>\n ) : null}\n </div>\n ) : (\n tickets.map((ticket) => (\n <div\n key={ticket.id}\n className=\"flex flex-col items-center gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-4\"\n >\n <div className=\"flex w-full items-center justify-between\">\n <span className=\"font-mono text-xs text-text-secondary\">{ticket.ticketCode}</span>\n <StatusBadge status={ticket.status} live={ticket.status === 'ISSUED'} />\n </div>\n\n {ticket.status === 'ISSUED' ? (\n <img\n src={ticket.qrCodeDataUrl}\n alt={t('ggnomad.ticket.qrAlt', 'QR code for ticket {{code}}').replace(\n '{{code}}',\n ticket.ticketCode\n )}\n className=\"size-48 rounded-md border border-border-seam bg-white p-2\"\n width={192}\n height={192}\n />\n ) : (\n <div className=\"flex size-48 items-center justify-center rounded-md border border-dashed border-border-seam bg-bg-sunken text-center text-xs text-text-secondary\">\n {ticket.status === 'REDEEMED'\n ? t('ggnomad.ticket.alreadyRedeemed', 'Already redeemed — QR retired')\n : t('ggnomad.ticket.revokedNotice', 'Revoked — no longer valid')}\n </div>\n )}\n\n <div className=\"w-full space-y-1 text-xs text-text-secondary\">\n <p>\n {t('ggnomad.ticket.issuedAt', 'Issued')} {formatDateTime(ticket.issuedAt)}\n </p>\n {ticket.redeemedAt ? (\n <p>\n {t('ggnomad.ticket.redeemedAt', 'Redeemed')} {formatDateTime(ticket.redeemedAt)}\n </p>\n ) : null}\n {ticket.revokedAt ? (\n <p>\n {t('ggnomad.ticket.revokedAt', 'Revoked')} {formatDateTime(ticket.revokedAt)}\n {ticket.revokedReason ? ` — ${ticket.revokedReason}` : ''}\n </p>\n ) : null}\n </div>\n </div>\n ))\n )}\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel>{t('ggnomad.ticket.close', 'Close')}</AlertDialogCancel>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":"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"}
@@ -8,4 +8,7 @@ export { DetailHero } from './DetailHero';
8
8
  export { BookingPanel } from './BookingPanel';
9
9
  export { ReviewsSection } from './ReviewsSection';
10
10
  export { ListingEditDialog } from './ListingEditDialog';
11
+ export { TicketDialog } from './TicketDialog';
12
+ export type { GgnomadTicket } from './TicketDialog';
13
+ export { RedeemTicketPanel } from './RedeemTicketPanel';
11
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -8,3 +8,5 @@ import "./DetailHero.js";
8
8
  import "./BookingPanel.js";
9
9
  import "./ReviewsSection.js";
10
10
  import "./ListingEditDialog.js";
11
+ import "./TicketDialog.js";
12
+ import "./RedeemTicketPanel.js";
@@ -29,6 +29,10 @@ export declare const GGNOMAD_FAVORITES_QUERY: import('graphql').DocumentNode;
29
29
  export declare const CREATE_GGNOMAD_BOOKING_MUTATION: import('graphql').DocumentNode;
30
30
  export declare const TOGGLE_GGNOMAD_FAVORITE_MUTATION: import('graphql').DocumentNode;
31
31
  export declare const CANCEL_GGNOMAD_BOOKING_MUTATION: import('graphql').DocumentNode;
32
+ export declare const GGNOMAD_TICKETS_QUERY: import('graphql').DocumentNode;
33
+ export declare const ISSUE_GGNOMAD_TICKET_MUTATION: import('graphql').DocumentNode;
34
+ export declare const REDEEM_GGNOMAD_TICKET_MUTATION: import('graphql').DocumentNode;
35
+ export declare const REVOKE_GGNOMAD_TICKET_MUTATION: import('graphql').DocumentNode;
32
36
  export declare const GGNOMAD_TRIP_PLANS_QUERY: import('graphql').DocumentNode;
33
37
  export declare const GGNOMAD_TRIP_PLAN_QUERY: import('graphql').DocumentNode;
34
38
  export declare const CREATE_GGNOMAD_TRIP_PLAN_MUTATION: import('graphql').DocumentNode;
@@ -1 +1 @@
1
- {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../src/ggnomad/graphql.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB,gCAgBlC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,gCA2BzC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,gCAmB1C,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAenC,CAAC;AAEF,eAAO,MAAM,wBAAwB,gCAkBpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCA4BlC,CAAC;AAGF,eAAO,MAAM,kCAAkC,gCAS9C,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAkB/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,gCAwB9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,gCAiBhC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAuB/B,CAAC;AAEF,eAAO,MAAM,wBAAwB,gCAoBpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCA0BlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAoB/B,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCAwBlC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAInC,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAe3C,CAAC;AAEF,eAAO,MAAM,gCAAgC,gCAI5C,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAO3C,CAAC;AAGF,eAAO,MAAM,wBAAwB,gCAepC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCA2BnC,CAAC;AAGF,eAAO,MAAM,iCAAiC,gCAe7C,CAAC;AAEF,eAAO,MAAM,iCAAiC,gCAe7C,CAAC;AAEF,eAAO,MAAM,iCAAiC,gCAI7C,CAAC;AAEF,eAAO,MAAM,mCAAmC,gCAc/C,CAAC;AAEF,eAAO,MAAM,sCAAsC,gCAIlD,CAAC;AAGF,eAAO,MAAM,4BAA4B,gCAQxC,CAAC;AAEF,eAAO,MAAM,qBAAqB,gCAwBjC,CAAC;AAGF,eAAO,MAAM,8BAA8B,gCAS1C,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAQ3C,CAAC;AAGF,eAAO,MAAM,gCAAgC,gCAW5C,CAAC;AAEF,eAAO,MAAM,4BAA4B,gCAWxC,CAAC;AAEF,eAAO,MAAM,6BAA6B,gCAWzC,CAAC;AAEF,eAAO,MAAM,gCAAgC,gCAW5C,CAAC"}
1
+ {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../src/ggnomad/graphql.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB,gCAgBlC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,gCA2BzC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,gCAmB1C,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAenC,CAAC;AAEF,eAAO,MAAM,wBAAwB,gCAkBpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCA4BlC,CAAC;AAGF,eAAO,MAAM,kCAAkC,gCAS9C,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAkB/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,gCAwB9B,CAAC;AAEF,eAAO,MAAM,oBAAoB,gCAiBhC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAuB/B,CAAC;AAEF,eAAO,MAAM,wBAAwB,gCAoBpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCA0BlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,gCAoB/B,CAAC;AAEF,eAAO,MAAM,sBAAsB,gCAwBlC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCAInC,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAe3C,CAAC;AAEF,eAAO,MAAM,gCAAgC,gCAI5C,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAO3C,CAAC;AAGF,eAAO,MAAM,qBAAqB,gCAejC,CAAC;AAEF,eAAO,MAAM,6BAA6B,gCAWzC,CAAC;AAEF,eAAO,MAAM,8BAA8B,gCAW1C,CAAC;AAEF,eAAO,MAAM,8BAA8B,gCAS1C,CAAC;AAGF,eAAO,MAAM,wBAAwB,gCAepC,CAAC;AAEF,eAAO,MAAM,uBAAuB,gCA2BnC,CAAC;AAGF,eAAO,MAAM,iCAAiC,gCAe7C,CAAC;AAEF,eAAO,MAAM,iCAAiC,gCAe7C,CAAC;AAEF,eAAO,MAAM,iCAAiC,gCAI7C,CAAC;AAEF,eAAO,MAAM,mCAAmC,gCAc/C,CAAC;AAEF,eAAO,MAAM,sCAAsC,gCAIlD,CAAC;AAGF,eAAO,MAAM,4BAA4B,gCAQxC,CAAC;AAEF,eAAO,MAAM,qBAAqB,gCAwBjC,CAAC;AAGF,eAAO,MAAM,8BAA8B,gCAS1C,CAAC;AAEF,eAAO,MAAM,+BAA+B,gCAQ3C,CAAC;AAGF,eAAO,MAAM,gCAAgC,gCAW5C,CAAC;AAEF,eAAO,MAAM,4BAA4B,gCAWxC,CAAC;AAEF,eAAO,MAAM,6BAA6B,gCAWzC,CAAC;AAEF,eAAO,MAAM,gCAAgC,gCAW5C,CAAC"}
@@ -337,6 +337,54 @@ var s = e`
337
337
  }
338
338
  }
339
339
  `, y = e`
340
+ query GGNomadTickets($bookingId: String) {
341
+ ggnomadTickets(bookingId: $bookingId) {
342
+ id
343
+ bookingId
344
+ ticketCode
345
+ qrCodeDataUrl
346
+ status
347
+ issuedAt
348
+ redeemedAt
349
+ redeemedBy
350
+ revokedAt
351
+ revokedReason
352
+ }
353
+ }
354
+ `, b = e`
355
+ mutation IssueGGNomadTicket($bookingId: ID!) {
356
+ issueGgnomadTicket(bookingId: $bookingId) {
357
+ id
358
+ bookingId
359
+ ticketCode
360
+ qrCodeDataUrl
361
+ status
362
+ issuedAt
363
+ }
364
+ }
365
+ `, x = e`
366
+ mutation RedeemGGNomadTicket($token: String!) {
367
+ redeemGgnomadTicket(token: $token) {
368
+ id
369
+ bookingId
370
+ ticketCode
371
+ status
372
+ redeemedAt
373
+ redeemedBy
374
+ }
375
+ }
376
+ `;
377
+ e`
378
+ mutation RevokeGGNomadTicket($id: ID!, $reason: String) {
379
+ revokeGgnomadTicket(id: $id, reason: $reason) {
380
+ id
381
+ status
382
+ revokedAt
383
+ revokedReason
384
+ }
385
+ }
386
+ `;
387
+ var S = e`
340
388
  query GGNomadTripPlans($limit: Int, $offset: Int) {
341
389
  ggnomadTripPlans(pagination: { limit: $limit, offset: $offset }) {
342
390
  id
@@ -351,7 +399,7 @@ var s = e`
351
399
  createdAt
352
400
  }
353
401
  }
354
- `, b = e`
402
+ `, C = e`
355
403
  query GGNomadTripPlan($id: ID!) {
356
404
  ggnomadTripPlan(id: $id) {
357
405
  id
@@ -378,7 +426,7 @@ var s = e`
378
426
  updatedAt
379
427
  }
380
428
  }
381
- `, x = e`
429
+ `, w = e`
382
430
  mutation CreateGGNomadTripPlan($input: CreateGgnomadTripPlanInput!) {
383
431
  createGgnomadTripPlan(input: $input) {
384
432
  id
@@ -393,7 +441,7 @@ var s = e`
393
441
  createdAt
394
442
  }
395
443
  }
396
- `, S = e`
444
+ `, T = e`
397
445
  mutation UpdateGGNomadTripPlan($id: ID!, $input: UpdateGgnomadTripPlanInput!) {
398
446
  updateGgnomadTripPlan(id: $id, input: $input) {
399
447
  id
@@ -414,7 +462,7 @@ e`
414
462
  deleteGgnomadTripPlan(id: $id)
415
463
  }
416
464
  `;
417
- var C = e`
465
+ var E = e`
418
466
  mutation AddGGNomadTripPlanItem($tripPlanId: ID!, $input: AddGgnomadTripPlanItemInput!) {
419
467
  addGgnomadTripPlanItem(tripPlanId: $tripPlanId, input: $input) {
420
468
  id
@@ -428,11 +476,11 @@ var C = e`
428
476
  notes
429
477
  }
430
478
  }
431
- `, w = e`
479
+ `, D = e`
432
480
  mutation RemoveGGNomadTripPlanItem($id: ID!) {
433
481
  removeGgnomadTripPlanItem(id: $id)
434
482
  }
435
- `, T = e`
483
+ `, O = e`
436
484
  query GGNomadReviewSummary($targetType: GgnomadBookingType!, $targetId: String!) {
437
485
  ggnomadReviewSummary(targetType: $targetType, targetId: $targetId) {
438
486
  average
@@ -440,7 +488,7 @@ var C = e`
440
488
  distribution
441
489
  }
442
490
  }
443
- `, E = e`
491
+ `, k = e`
444
492
  query GGNomadReviews(
445
493
  $targetType: GgnomadBookingType!
446
494
  $targetId: String!
@@ -464,7 +512,7 @@ var C = e`
464
512
  createdAt
465
513
  }
466
514
  }
467
- `, D = e`
515
+ `, A = e`
468
516
  mutation CreateGGNomadReview($input: CreateGgnomadReviewInput!) {
469
517
  createGgnomadReview(input: $input) {
470
518
  id
@@ -483,7 +531,7 @@ e`
483
531
  }
484
532
  }
485
533
  `;
486
- var O = e`
534
+ var j = e`
487
535
  mutation UpdateGGNomadProperty($id: ID!, $input: UpdateGgnomadPropertyInput!) {
488
536
  updateGgnomadProperty(id: $id, input: $input) {
489
537
  id
@@ -494,7 +542,7 @@ var O = e`
494
542
  images
495
543
  }
496
544
  }
497
- `, k = e`
545
+ `, M = e`
498
546
  mutation UpdateGGNomadTour($id: ID!, $input: UpdateGgnomadTourInput!) {
499
547
  updateGgnomadTour(id: $id, input: $input) {
500
548
  id
@@ -505,7 +553,7 @@ var O = e`
505
553
  images
506
554
  }
507
555
  }
508
- `, A = e`
556
+ `, N = e`
509
557
  mutation UpdateGGNomadEvent($id: ID!, $input: UpdateGgnomadEventInput!) {
510
558
  updateGgnomadEvent(id: $id, input: $input) {
511
559
  id
@@ -516,7 +564,7 @@ var O = e`
516
564
  images
517
565
  }
518
566
  }
519
- `, j = e`
567
+ `, P = e`
520
568
  mutation UpdateGGNomadActivity($id: ID!, $input: UpdateGgnomadActivityInput!) {
521
569
  updateGgnomadActivity(id: $id, input: $input) {
522
570
  id
@@ -529,6 +577,6 @@ var O = e`
529
577
  }
530
578
  `;
531
579
  //#endregion
532
- export { C as ADD_GGNOMAD_TRIP_PLAN_ITEM_MUTATION, v as CANCEL_GGNOMAD_BOOKING_MUTATION, g as CREATE_GGNOMAD_BOOKING_MUTATION, D as CREATE_GGNOMAD_REVIEW_MUTATION, x as CREATE_GGNOMAD_TRIP_PLAN_MUTATION, d as GGNOMAD_ACTIVITIES_QUERY, f as GGNOMAD_ACTIVITY_QUERY, n as GGNOMAD_BOOKABLE_SAMPLE_QUERY, m as GGNOMAD_BOOKINGS_QUERY, l as GGNOMAD_EVENTS_QUERY, u as GGNOMAD_EVENT_QUERY, h as GGNOMAD_FAVORITES_QUERY, r as GGNOMAD_FAVORITE_TARGETS_QUERY, t as GGNOMAD_OVERVIEW_QUERY, a as GGNOMAD_PROPERTIES_QUERY, o as GGNOMAD_PROPERTY_QUERY, i as GGNOMAD_PROVIDERS_QUERY, E as GGNOMAD_REVIEWS_QUERY, T as GGNOMAD_REVIEW_SUMMARY_QUERY, s as GGNOMAD_TOURS_QUERY, c as GGNOMAD_TOUR_QUERY, p as GGNOMAD_TRIPS_QUERY, y as GGNOMAD_TRIP_PLANS_QUERY, b as GGNOMAD_TRIP_PLAN_QUERY, w as REMOVE_GGNOMAD_TRIP_PLAN_ITEM_MUTATION, _ as TOGGLE_GGNOMAD_FAVORITE_MUTATION, j as UPDATE_GGNOMAD_ACTIVITY_MUTATION, A as UPDATE_GGNOMAD_EVENT_MUTATION, O as UPDATE_GGNOMAD_PROPERTY_MUTATION, k as UPDATE_GGNOMAD_TOUR_MUTATION, S as UPDATE_GGNOMAD_TRIP_PLAN_MUTATION };
580
+ export { E as ADD_GGNOMAD_TRIP_PLAN_ITEM_MUTATION, v as CANCEL_GGNOMAD_BOOKING_MUTATION, g as CREATE_GGNOMAD_BOOKING_MUTATION, A as CREATE_GGNOMAD_REVIEW_MUTATION, w as CREATE_GGNOMAD_TRIP_PLAN_MUTATION, d as GGNOMAD_ACTIVITIES_QUERY, f as GGNOMAD_ACTIVITY_QUERY, n as GGNOMAD_BOOKABLE_SAMPLE_QUERY, m as GGNOMAD_BOOKINGS_QUERY, l as GGNOMAD_EVENTS_QUERY, u as GGNOMAD_EVENT_QUERY, h as GGNOMAD_FAVORITES_QUERY, r as GGNOMAD_FAVORITE_TARGETS_QUERY, t as GGNOMAD_OVERVIEW_QUERY, a as GGNOMAD_PROPERTIES_QUERY, o as GGNOMAD_PROPERTY_QUERY, i as GGNOMAD_PROVIDERS_QUERY, k as GGNOMAD_REVIEWS_QUERY, O as GGNOMAD_REVIEW_SUMMARY_QUERY, y as GGNOMAD_TICKETS_QUERY, s as GGNOMAD_TOURS_QUERY, c as GGNOMAD_TOUR_QUERY, p as GGNOMAD_TRIPS_QUERY, S as GGNOMAD_TRIP_PLANS_QUERY, C as GGNOMAD_TRIP_PLAN_QUERY, b as ISSUE_GGNOMAD_TICKET_MUTATION, x as REDEEM_GGNOMAD_TICKET_MUTATION, D as REMOVE_GGNOMAD_TRIP_PLAN_ITEM_MUTATION, _ as TOGGLE_GGNOMAD_FAVORITE_MUTATION, P as UPDATE_GGNOMAD_ACTIVITY_MUTATION, N as UPDATE_GGNOMAD_EVENT_MUTATION, j as UPDATE_GGNOMAD_PROPERTY_MUTATION, M as UPDATE_GGNOMAD_TOUR_MUTATION, T as UPDATE_GGNOMAD_TRIP_PLAN_MUTATION };
533
581
 
534
582
  //# sourceMappingURL=graphql.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graphql.js","names":[],"sources":["../../src/ggnomad/graphql.ts"],"sourcesContent":["import { gql } from '@apollo/client/core';\n\nexport const GGNOMAD_OVERVIEW_QUERY = gql`\n query GGNomadOverview {\n ggnomadOverviewStats {\n users\n listings\n bookings\n monthlyRevenue\n }\n ggnomadAnalytics {\n totalRevenue\n totalBookings\n totalUsers\n conversionRate\n revenueByCategory\n }\n }\n`;\n\n/**\n * One bookable item of each type (properties are priced per night, the rest\n * carry a flat price). Used to seed the Bookings \"create demo booking\" action\n * with a REAL listing id so the mutation passes the backend's\n * assertTargetInWorkspace check instead of failing on a synthetic id.\n */\nexport const GGNOMAD_BOOKABLE_SAMPLE_QUERY = gql`\n query GGNomadBookableSample {\n ggnomadProperties(pagination: { limit: 1, offset: 0 }) {\n id\n name\n pricePerNight\n currency\n }\n ggnomadTours(pagination: { limit: 1, offset: 0 }) {\n id\n name\n price\n currency\n }\n ggnomadEvents(pagination: { limit: 1, offset: 0 }) {\n id\n name\n price\n currency\n }\n ggnomadActivities(pagination: { limit: 1, offset: 0 }) {\n id\n name\n price\n currency\n }\n }\n`;\n\n/**\n * id + name for every favouritable listing type, used to (a) resolve the real\n * entity name for a saved favorite (which the backend only exposes as\n * targetType + targetId) and (b) seed the \"toggle demo favorite\" action with a\n * real target id.\n */\nexport const GGNOMAD_FAVORITE_TARGETS_QUERY = gql`\n query GGNomadFavoriteTargets {\n ggnomadProperties(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n ggnomadTours(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n ggnomadEvents(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n ggnomadActivities(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n }\n`;\n\nexport const GGNOMAD_PROVIDERS_QUERY = gql`\n query GGNomadProviders($limit: Int, $offset: Int) {\n ggnomadProviders(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n type\n status\n listingCount\n totalRevenue\n rating\n reviewCount\n contactEmail\n website\n }\n }\n`;\n\nexport const GGNOMAD_PROPERTIES_QUERY = gql`\n query GGNomadProperties($limit: Int, $offset: Int) {\n ggnomadProperties(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n type\n status\n description\n pricePerNight\n currency\n rating\n reviewCount\n maxGuests\n amenities\n images\n providerId\n }\n }\n`;\n\nexport const GGNOMAD_PROPERTY_QUERY = gql`\n query GGNomadProperty($id: ID!) {\n ggnomadProperty(id: $id) {\n id\n name\n type\n status\n description\n address\n pricePerNight\n currency\n rating\n reviewCount\n bedrooms\n bathrooms\n maxGuests\n checkInTime\n checkOutTime\n amenities\n images\n provider {\n id\n name\n type\n rating\n }\n }\n }\n`;\n\n// Query to get property details for booking - ensures id is included\nexport const GGNOMAD_PROPERTY_FOR_BOOKING_QUERY = gql`\n query GGNomadPropertyForBooking($id: ID!) {\n ggnomadProperty(id: $id) {\n id\n name\n pricePerNight\n currency\n }\n }\n`;\n\nexport const GGNOMAD_TOURS_QUERY = gql`\n query GGNomadTours($limit: Int, $offset: Int) {\n ggnomadTours(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n type\n status\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n highlights\n images\n }\n }\n`;\n\nexport const GGNOMAD_TOUR_QUERY = gql`\n query GGNomadTour($id: ID!) {\n ggnomadTour(id: $id) {\n id\n name\n description\n type\n status\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n highlights\n images\n languages\n guideName\n provider {\n id\n name\n }\n }\n }\n`;\n\nexport const GGNOMAD_EVENTS_QUERY = gql`\n query GGNomadEvents($limit: Int, $offset: Int) {\n ggnomadEvents(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n category\n status\n startsAt\n price\n currency\n rating\n reviewCount\n performers\n images\n }\n }\n`;\n\nexport const GGNOMAD_EVENT_QUERY = gql`\n query GGNomadEvent($id: ID!) {\n ggnomadEvent(id: $id) {\n id\n name\n description\n category\n status\n startsAt\n endsAt\n venue\n price\n currency\n rating\n reviewCount\n performers\n images\n provider {\n id\n name\n }\n }\n }\n`;\n\nexport const GGNOMAD_ACTIVITIES_QUERY = gql`\n query GGNomadActivities($limit: Int, $offset: Int) {\n ggnomadActivities(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n category\n status\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n maxParticipants\n includes\n requirements\n images\n }\n }\n`;\n\nexport const GGNOMAD_ACTIVITY_QUERY = gql`\n query GGNomadActivity($id: ID!) {\n ggnomadActivity(id: $id) {\n id\n name\n description\n category\n status\n location\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n maxParticipants\n includes\n requirements\n safetyInfo\n images\n provider {\n id\n name\n }\n }\n }\n`;\n\nexport const GGNOMAD_TRIPS_QUERY = gql`\n query GGNomadTrips($limit: Int, $offset: Int) {\n ggnomadTrips(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n transportMode\n status\n origin\n destination\n departureAt\n arrivalAt\n isRoundTrip\n carrier\n class\n price\n currency\n passengers\n ecoFriendly\n }\n }\n`;\n\nexport const GGNOMAD_BOOKINGS_QUERY = gql`\n query GGNomadBookings($limit: Int, $offset: Int) {\n ggnomadBookings(pagination: { limit: $limit, offset: $offset }) {\n id\n referenceCode\n bookingType\n status\n itemId\n itemName\n guestName\n guestEmail\n guestPhone\n guests\n checkIn\n checkOut\n eventDate\n totalPrice\n currency\n paymentStatus\n paymentMethod\n specialRequests\n createdAt\n }\n }\n`;\n\nexport const GGNOMAD_FAVORITES_QUERY = gql`\n query GGNomadFavorites {\n ggnomadFavorites\n }\n`;\n\nexport const CREATE_GGNOMAD_BOOKING_MUTATION = gql`\n mutation CreateGGNomadBooking($input: CreateGgnomadBookingInput!) {\n createGgnomadBooking(input: $input) {\n id\n referenceCode\n bookingType\n status\n itemId\n itemName\n guestName\n totalPrice\n currency\n createdAt\n }\n }\n`;\n\nexport const TOGGLE_GGNOMAD_FAVORITE_MUTATION = gql`\n mutation ToggleGGNomadFavorite($input: ToggleGgnomadFavoriteInput!) {\n toggleGgnomadFavorite(input: $input)\n }\n`;\n\nexport const CANCEL_GGNOMAD_BOOKING_MUTATION = gql`\n mutation CancelGGNomadBooking($id: ID!, $reason: String) {\n cancelGgnomadBooking(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\n// Trip Plan Queries\nexport const GGNOMAD_TRIP_PLANS_QUERY = gql`\n query GGNomadTripPlans($limit: Int, $offset: Int) {\n ggnomadTripPlans(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n createdAt\n }\n }\n`;\n\nexport const GGNOMAD_TRIP_PLAN_QUERY = gql`\n query GGNomadTripPlan($id: ID!) {\n ggnomadTripPlan(id: $id) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n items {\n id\n itemType\n itemId\n itemName\n day\n scheduledAt\n price\n currency\n notes\n }\n createdAt\n updatedAt\n }\n }\n`;\n\n// Trip Plan Mutations\nexport const CREATE_GGNOMAD_TRIP_PLAN_MUTATION = gql`\n mutation CreateGGNomadTripPlan($input: CreateGgnomadTripPlanInput!) {\n createGgnomadTripPlan(input: $input) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n createdAt\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_TRIP_PLAN_MUTATION = gql`\n mutation UpdateGGNomadTripPlan($id: ID!, $input: UpdateGgnomadTripPlanInput!) {\n updateGgnomadTripPlan(id: $id, input: $input) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n updatedAt\n }\n }\n`;\n\nexport const DELETE_GGNOMAD_TRIP_PLAN_MUTATION = gql`\n mutation DeleteGGNomadTripPlan($id: ID!) {\n deleteGgnomadTripPlan(id: $id)\n }\n`;\n\nexport const ADD_GGNOMAD_TRIP_PLAN_ITEM_MUTATION = gql`\n mutation AddGGNomadTripPlanItem($tripPlanId: ID!, $input: AddGgnomadTripPlanItemInput!) {\n addGgnomadTripPlanItem(tripPlanId: $tripPlanId, input: $input) {\n id\n itemType\n itemId\n itemName\n day\n scheduledAt\n price\n currency\n notes\n }\n }\n`;\n\nexport const REMOVE_GGNOMAD_TRIP_PLAN_ITEM_MUTATION = gql`\n mutation RemoveGGNomadTripPlanItem($id: ID!) {\n removeGgnomadTripPlanItem(id: $id)\n }\n`;\n\n// Review Queries\nexport const GGNOMAD_REVIEW_SUMMARY_QUERY = gql`\n query GGNomadReviewSummary($targetType: GgnomadBookingType!, $targetId: String!) {\n ggnomadReviewSummary(targetType: $targetType, targetId: $targetId) {\n average\n count\n distribution\n }\n }\n`;\n\nexport const GGNOMAD_REVIEWS_QUERY = gql`\n query GGNomadReviews(\n $targetType: GgnomadBookingType!\n $targetId: String!\n $limit: Int\n $offset: Int\n ) {\n ggnomadReviews(\n targetType: $targetType\n targetId: $targetId\n pagination: { limit: $limit, offset: $offset }\n ) {\n id\n rating\n title\n content\n images\n isVerified\n helpfulCount\n response\n respondedAt\n createdAt\n }\n }\n`;\n\n// Review Mutations\nexport const CREATE_GGNOMAD_REVIEW_MUTATION = gql`\n mutation CreateGGNomadReview($input: CreateGgnomadReviewInput!) {\n createGgnomadReview(input: $input) {\n id\n rating\n title\n content\n }\n }\n`;\n\nexport const RESPOND_GGNOMAD_REVIEW_MUTATION = gql`\n mutation RespondGGNomadReview($id: ID!, $response: String!) {\n respondGgnomadReview(id: $id, response: $response) {\n id\n response\n respondedAt\n }\n }\n`;\n\n// Listing Edit Mutations\nexport const UPDATE_GGNOMAD_PROPERTY_MUTATION = gql`\n mutation UpdateGGNomadProperty($id: ID!, $input: UpdateGgnomadPropertyInput!) {\n updateGgnomadProperty(id: $id, input: $input) {\n id\n name\n description\n pricePerNight\n status\n images\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_TOUR_MUTATION = gql`\n mutation UpdateGGNomadTour($id: ID!, $input: UpdateGgnomadTourInput!) {\n updateGgnomadTour(id: $id, input: $input) {\n id\n name\n description\n price\n status\n images\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_EVENT_MUTATION = gql`\n mutation UpdateGGNomadEvent($id: ID!, $input: UpdateGgnomadEventInput!) {\n updateGgnomadEvent(id: $id, input: $input) {\n id\n name\n description\n price\n status\n images\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_ACTIVITY_MUTATION = gql`\n mutation UpdateGGNomadActivity($id: ID!, $input: UpdateGgnomadActivityInput!) {\n updateGgnomadActivity(id: $id, input: $input) {\n id\n name\n description\n price\n status\n images\n }\n }\n`;\n"],"mappings":";;AAEA,IAAa,IAAyB,CAAG;;;;;;;;;;;;;;;;GAwB5B,IAAgC,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCnC,IAAiC,CAAG;;;;;;;;;;;;;;;;;;;GAqBpC,IAA0B,CAAG;;;;;;;;;;;;;;;GAiB7B,IAA2B,CAAG;;;;;;;;;;;;;;;;;;GAoB9B,IAAyB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BS,CAAG;;;;;;;;;;AAWrD,IAAa,IAAsB,CAAG;;;;;;;;;;;;;;;;;;GAoBzB,IAAqB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;GA0BxB,IAAuB,CAAG;;;;;;;;;;;;;;;;;GAmB1B,IAAsB,CAAG;;;;;;;;;;;;;;;;;;;;;;;GAyBzB,IAA2B,CAAG;;;;;;;;;;;;;;;;;;;;GAsB9B,IAAyB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;GA4B5B,IAAsB,CAAG;;;;;;;;;;;;;;;;;;;;GAsBzB,IAAyB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;GA0B5B,IAA0B,CAAG;;;;GAM7B,IAAkC,CAAG;;;;;;;;;;;;;;;GAiBrC,IAAmC,CAAG;;;;GAMtC,IAAkC,CAAG;;;;;;;GAUrC,IAA2B,CAAG;;;;;;;;;;;;;;;GAiB9B,IAA0B,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8B7B,IAAoC,CAAG;;;;;;;;;;;;;;;GAiBvC,IAAoC,CAAG;;;;;;;;;;;;;;;;AAiBH,CAAG;;;;;AAMpD,IAAa,IAAsC,CAAG;;;;;;;;;;;;;;GAgBzC,IAAyC,CAAG;;;;GAO5C,IAA+B,CAAG;;;;;;;;GAUlC,IAAwB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;GA2B3B,IAAiC,CAAG;;;;;;;;;;AAWF,CAAG;;;;;;;;;AAWlD,IAAa,IAAmC,CAAG;;;;;;;;;;;GAatC,IAA+B,CAAG;;;;;;;;;;;GAalC,IAAgC,CAAG;;;;;;;;;;;GAanC,IAAmC,CAAG"}
1
+ {"version":3,"file":"graphql.js","names":[],"sources":["../../src/ggnomad/graphql.ts"],"sourcesContent":["import { gql } from '@apollo/client/core';\n\nexport const GGNOMAD_OVERVIEW_QUERY = gql`\n query GGNomadOverview {\n ggnomadOverviewStats {\n users\n listings\n bookings\n monthlyRevenue\n }\n ggnomadAnalytics {\n totalRevenue\n totalBookings\n totalUsers\n conversionRate\n revenueByCategory\n }\n }\n`;\n\n/**\n * One bookable item of each type (properties are priced per night, the rest\n * carry a flat price). Used to seed the Bookings \"create demo booking\" action\n * with a REAL listing id so the mutation passes the backend's\n * assertTargetInWorkspace check instead of failing on a synthetic id.\n */\nexport const GGNOMAD_BOOKABLE_SAMPLE_QUERY = gql`\n query GGNomadBookableSample {\n ggnomadProperties(pagination: { limit: 1, offset: 0 }) {\n id\n name\n pricePerNight\n currency\n }\n ggnomadTours(pagination: { limit: 1, offset: 0 }) {\n id\n name\n price\n currency\n }\n ggnomadEvents(pagination: { limit: 1, offset: 0 }) {\n id\n name\n price\n currency\n }\n ggnomadActivities(pagination: { limit: 1, offset: 0 }) {\n id\n name\n price\n currency\n }\n }\n`;\n\n/**\n * id + name for every favouritable listing type, used to (a) resolve the real\n * entity name for a saved favorite (which the backend only exposes as\n * targetType + targetId) and (b) seed the \"toggle demo favorite\" action with a\n * real target id.\n */\nexport const GGNOMAD_FAVORITE_TARGETS_QUERY = gql`\n query GGNomadFavoriteTargets {\n ggnomadProperties(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n ggnomadTours(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n ggnomadEvents(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n ggnomadActivities(pagination: { limit: 100, offset: 0 }) {\n id\n name\n }\n }\n`;\n\nexport const GGNOMAD_PROVIDERS_QUERY = gql`\n query GGNomadProviders($limit: Int, $offset: Int) {\n ggnomadProviders(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n type\n status\n listingCount\n totalRevenue\n rating\n reviewCount\n contactEmail\n website\n }\n }\n`;\n\nexport const GGNOMAD_PROPERTIES_QUERY = gql`\n query GGNomadProperties($limit: Int, $offset: Int) {\n ggnomadProperties(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n type\n status\n description\n pricePerNight\n currency\n rating\n reviewCount\n maxGuests\n amenities\n images\n providerId\n }\n }\n`;\n\nexport const GGNOMAD_PROPERTY_QUERY = gql`\n query GGNomadProperty($id: ID!) {\n ggnomadProperty(id: $id) {\n id\n name\n type\n status\n description\n address\n pricePerNight\n currency\n rating\n reviewCount\n bedrooms\n bathrooms\n maxGuests\n checkInTime\n checkOutTime\n amenities\n images\n provider {\n id\n name\n type\n rating\n }\n }\n }\n`;\n\n// Query to get property details for booking - ensures id is included\nexport const GGNOMAD_PROPERTY_FOR_BOOKING_QUERY = gql`\n query GGNomadPropertyForBooking($id: ID!) {\n ggnomadProperty(id: $id) {\n id\n name\n pricePerNight\n currency\n }\n }\n`;\n\nexport const GGNOMAD_TOURS_QUERY = gql`\n query GGNomadTours($limit: Int, $offset: Int) {\n ggnomadTours(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n type\n status\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n highlights\n images\n }\n }\n`;\n\nexport const GGNOMAD_TOUR_QUERY = gql`\n query GGNomadTour($id: ID!) {\n ggnomadTour(id: $id) {\n id\n name\n description\n type\n status\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n highlights\n images\n languages\n guideName\n provider {\n id\n name\n }\n }\n }\n`;\n\nexport const GGNOMAD_EVENTS_QUERY = gql`\n query GGNomadEvents($limit: Int, $offset: Int) {\n ggnomadEvents(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n category\n status\n startsAt\n price\n currency\n rating\n reviewCount\n performers\n images\n }\n }\n`;\n\nexport const GGNOMAD_EVENT_QUERY = gql`\n query GGNomadEvent($id: ID!) {\n ggnomadEvent(id: $id) {\n id\n name\n description\n category\n status\n startsAt\n endsAt\n venue\n price\n currency\n rating\n reviewCount\n performers\n images\n provider {\n id\n name\n }\n }\n }\n`;\n\nexport const GGNOMAD_ACTIVITIES_QUERY = gql`\n query GGNomadActivities($limit: Int, $offset: Int) {\n ggnomadActivities(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n category\n status\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n maxParticipants\n includes\n requirements\n images\n }\n }\n`;\n\nexport const GGNOMAD_ACTIVITY_QUERY = gql`\n query GGNomadActivity($id: ID!) {\n ggnomadActivity(id: $id) {\n id\n name\n description\n category\n status\n location\n duration\n difficulty\n price\n currency\n rating\n reviewCount\n maxParticipants\n includes\n requirements\n safetyInfo\n images\n provider {\n id\n name\n }\n }\n }\n`;\n\nexport const GGNOMAD_TRIPS_QUERY = gql`\n query GGNomadTrips($limit: Int, $offset: Int) {\n ggnomadTrips(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n transportMode\n status\n origin\n destination\n departureAt\n arrivalAt\n isRoundTrip\n carrier\n class\n price\n currency\n passengers\n ecoFriendly\n }\n }\n`;\n\nexport const GGNOMAD_BOOKINGS_QUERY = gql`\n query GGNomadBookings($limit: Int, $offset: Int) {\n ggnomadBookings(pagination: { limit: $limit, offset: $offset }) {\n id\n referenceCode\n bookingType\n status\n itemId\n itemName\n guestName\n guestEmail\n guestPhone\n guests\n checkIn\n checkOut\n eventDate\n totalPrice\n currency\n paymentStatus\n paymentMethod\n specialRequests\n createdAt\n }\n }\n`;\n\nexport const GGNOMAD_FAVORITES_QUERY = gql`\n query GGNomadFavorites {\n ggnomadFavorites\n }\n`;\n\nexport const CREATE_GGNOMAD_BOOKING_MUTATION = gql`\n mutation CreateGGNomadBooking($input: CreateGgnomadBookingInput!) {\n createGgnomadBooking(input: $input) {\n id\n referenceCode\n bookingType\n status\n itemId\n itemName\n guestName\n totalPrice\n currency\n createdAt\n }\n }\n`;\n\nexport const TOGGLE_GGNOMAD_FAVORITE_MUTATION = gql`\n mutation ToggleGGNomadFavorite($input: ToggleGgnomadFavoriteInput!) {\n toggleGgnomadFavorite(input: $input)\n }\n`;\n\nexport const CANCEL_GGNOMAD_BOOKING_MUTATION = gql`\n mutation CancelGGNomadBooking($id: ID!, $reason: String) {\n cancelGgnomadBooking(id: $id, reason: $reason) {\n id\n status\n }\n }\n`;\n\n// Mobile tickets / QR-code redemption (BOFF-4362)\nexport const GGNOMAD_TICKETS_QUERY = gql`\n query GGNomadTickets($bookingId: String) {\n ggnomadTickets(bookingId: $bookingId) {\n id\n bookingId\n ticketCode\n qrCodeDataUrl\n status\n issuedAt\n redeemedAt\n redeemedBy\n revokedAt\n revokedReason\n }\n }\n`;\n\nexport const ISSUE_GGNOMAD_TICKET_MUTATION = gql`\n mutation IssueGGNomadTicket($bookingId: ID!) {\n issueGgnomadTicket(bookingId: $bookingId) {\n id\n bookingId\n ticketCode\n qrCodeDataUrl\n status\n issuedAt\n }\n }\n`;\n\nexport const REDEEM_GGNOMAD_TICKET_MUTATION = gql`\n mutation RedeemGGNomadTicket($token: String!) {\n redeemGgnomadTicket(token: $token) {\n id\n bookingId\n ticketCode\n status\n redeemedAt\n redeemedBy\n }\n }\n`;\n\nexport const REVOKE_GGNOMAD_TICKET_MUTATION = gql`\n mutation RevokeGGNomadTicket($id: ID!, $reason: String) {\n revokeGgnomadTicket(id: $id, reason: $reason) {\n id\n status\n revokedAt\n revokedReason\n }\n }\n`;\n\n// Trip Plan Queries\nexport const GGNOMAD_TRIP_PLANS_QUERY = gql`\n query GGNomadTripPlans($limit: Int, $offset: Int) {\n ggnomadTripPlans(pagination: { limit: $limit, offset: $offset }) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n createdAt\n }\n }\n`;\n\nexport const GGNOMAD_TRIP_PLAN_QUERY = gql`\n query GGNomadTripPlan($id: ID!) {\n ggnomadTripPlan(id: $id) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n items {\n id\n itemType\n itemId\n itemName\n day\n scheduledAt\n price\n currency\n notes\n }\n createdAt\n updatedAt\n }\n }\n`;\n\n// Trip Plan Mutations\nexport const CREATE_GGNOMAD_TRIP_PLAN_MUTATION = gql`\n mutation CreateGGNomadTripPlan($input: CreateGgnomadTripPlanInput!) {\n createGgnomadTripPlan(input: $input) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n createdAt\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_TRIP_PLAN_MUTATION = gql`\n mutation UpdateGGNomadTripPlan($id: ID!, $input: UpdateGgnomadTripPlanInput!) {\n updateGgnomadTripPlan(id: $id, input: $input) {\n id\n name\n description\n startDate\n endDate\n travelers\n status\n total\n itemCount\n updatedAt\n }\n }\n`;\n\nexport const DELETE_GGNOMAD_TRIP_PLAN_MUTATION = gql`\n mutation DeleteGGNomadTripPlan($id: ID!) {\n deleteGgnomadTripPlan(id: $id)\n }\n`;\n\nexport const ADD_GGNOMAD_TRIP_PLAN_ITEM_MUTATION = gql`\n mutation AddGGNomadTripPlanItem($tripPlanId: ID!, $input: AddGgnomadTripPlanItemInput!) {\n addGgnomadTripPlanItem(tripPlanId: $tripPlanId, input: $input) {\n id\n itemType\n itemId\n itemName\n day\n scheduledAt\n price\n currency\n notes\n }\n }\n`;\n\nexport const REMOVE_GGNOMAD_TRIP_PLAN_ITEM_MUTATION = gql`\n mutation RemoveGGNomadTripPlanItem($id: ID!) {\n removeGgnomadTripPlanItem(id: $id)\n }\n`;\n\n// Review Queries\nexport const GGNOMAD_REVIEW_SUMMARY_QUERY = gql`\n query GGNomadReviewSummary($targetType: GgnomadBookingType!, $targetId: String!) {\n ggnomadReviewSummary(targetType: $targetType, targetId: $targetId) {\n average\n count\n distribution\n }\n }\n`;\n\nexport const GGNOMAD_REVIEWS_QUERY = gql`\n query GGNomadReviews(\n $targetType: GgnomadBookingType!\n $targetId: String!\n $limit: Int\n $offset: Int\n ) {\n ggnomadReviews(\n targetType: $targetType\n targetId: $targetId\n pagination: { limit: $limit, offset: $offset }\n ) {\n id\n rating\n title\n content\n images\n isVerified\n helpfulCount\n response\n respondedAt\n createdAt\n }\n }\n`;\n\n// Review Mutations\nexport const CREATE_GGNOMAD_REVIEW_MUTATION = gql`\n mutation CreateGGNomadReview($input: CreateGgnomadReviewInput!) {\n createGgnomadReview(input: $input) {\n id\n rating\n title\n content\n }\n }\n`;\n\nexport const RESPOND_GGNOMAD_REVIEW_MUTATION = gql`\n mutation RespondGGNomadReview($id: ID!, $response: String!) {\n respondGgnomadReview(id: $id, response: $response) {\n id\n response\n respondedAt\n }\n }\n`;\n\n// Listing Edit Mutations\nexport const UPDATE_GGNOMAD_PROPERTY_MUTATION = gql`\n mutation UpdateGGNomadProperty($id: ID!, $input: UpdateGgnomadPropertyInput!) {\n updateGgnomadProperty(id: $id, input: $input) {\n id\n name\n description\n pricePerNight\n status\n images\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_TOUR_MUTATION = gql`\n mutation UpdateGGNomadTour($id: ID!, $input: UpdateGgnomadTourInput!) {\n updateGgnomadTour(id: $id, input: $input) {\n id\n name\n description\n price\n status\n images\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_EVENT_MUTATION = gql`\n mutation UpdateGGNomadEvent($id: ID!, $input: UpdateGgnomadEventInput!) {\n updateGgnomadEvent(id: $id, input: $input) {\n id\n name\n description\n price\n status\n images\n }\n }\n`;\n\nexport const UPDATE_GGNOMAD_ACTIVITY_MUTATION = gql`\n mutation UpdateGGNomadActivity($id: ID!, $input: UpdateGgnomadActivityInput!) {\n updateGgnomadActivity(id: $id, input: $input) {\n id\n name\n description\n price\n status\n images\n }\n }\n`;\n"],"mappings":";;AAEA,IAAa,IAAyB,CAAG;;;;;;;;;;;;;;;;GAwB5B,IAAgC,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCnC,IAAiC,CAAG;;;;;;;;;;;;;;;;;;;GAqBpC,IAA0B,CAAG;;;;;;;;;;;;;;;GAiB7B,IAA2B,CAAG;;;;;;;;;;;;;;;;;;GAoB9B,IAAyB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BS,CAAG;;;;;;;;;;AAWrD,IAAa,IAAsB,CAAG;;;;;;;;;;;;;;;;;;GAoBzB,IAAqB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;GA0BxB,IAAuB,CAAG;;;;;;;;;;;;;;;;;GAmB1B,IAAsB,CAAG;;;;;;;;;;;;;;;;;;;;;;;GAyBzB,IAA2B,CAAG;;;;;;;;;;;;;;;;;;;;GAsB9B,IAAyB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;GA4B5B,IAAsB,CAAG;;;;;;;;;;;;;;;;;;;;GAsBzB,IAAyB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;GA0B5B,IAA0B,CAAG;;;;GAM7B,IAAkC,CAAG;;;;;;;;;;;;;;;GAiBrC,IAAmC,CAAG;;;;GAMtC,IAAkC,CAAG;;;;;;;GAUrC,IAAwB,CAAG;;;;;;;;;;;;;;;GAiB3B,IAAgC,CAAG;;;;;;;;;;;GAanC,IAAiC,CAAG;;;;;;;;;;;;AAaH,CAAG;;;;;;;;;;AAYjD,IAAa,IAA2B,CAAG;;;;;;;;;;;;;;;GAiB9B,IAA0B,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8B7B,IAAoC,CAAG;;;;;;;;;;;;;;;GAiBvC,IAAoC,CAAG;;;;;;;;;;;;;;;;AAiBH,CAAG;;;;;AAMpD,IAAa,IAAsC,CAAG;;;;;;;;;;;;;;GAgBzC,IAAyC,CAAG;;;;GAO5C,IAA+B,CAAG;;;;;;;;GAUlC,IAAwB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;GA2B3B,IAAiC,CAAG;;;;;;;;;;AAWF,CAAG;;;;;;;;;AAWlD,IAAa,IAAmC,CAAG;;;;;;;;;;;GAatC,IAA+B,CAAG;;;;;;;;;;;GAalC,IAAgC,CAAG;;;;;;;;;;;GAanC,IAAmC,CAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"BookingsPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/BookingsPage.tsx"],"names":[],"mappings":"AAsCA,wBAAgB,YAAY,gCAkY3B"}
1
+ {"version":3,"file":"BookingsPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/BookingsPage.tsx"],"names":[],"mappings":"AAwCA,wBAAgB,YAAY,gCA2Z3B"}
@@ -3,32 +3,34 @@ import { PageShell as i } from "./PageShell.js";
3
3
  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
+ import { TicketDialog as c } from "../components/TicketDialog.js";
7
+ import { RedeemTicketPanel as l } from "../components/RedeemTicketPanel.js";
6
8
  import "../components/index.js";
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";
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-dom";
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";
14
16
  //#region src/ggnomad/pages/BookingsPage.tsx
15
- function F() {
16
- let { t: F } = g(), I = _(), [L, R] = l(null), { data: z, refetch: B } = y(r, { variables: {
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: {
17
19
  limit: 20,
18
20
  offset: 0
19
- } }), { data: V } = y(n), [H, { loading: U }] = v(t), [W, { loading: G }] = v(e), K = c(() => z?.ggnomadBookings ?? [], [z?.ggnomadBookings]), q = c(() => {
21
+ } }), { data: K } = S(n), [q, { loading: J }] = x(t), [Y, { loading: X }] = x(e), Z = u(() => W?.ggnomadBookings ?? [], [W?.ggnomadBookings]), Q = u(() => {
20
22
  let e = {
21
23
  CONFIRMED: 0,
22
24
  CHECKED_IN: 0,
23
25
  COMPLETED: 0,
24
26
  CANCELLED: 0
25
27
  };
26
- return K.forEach((t) => {
28
+ return Z.forEach((t) => {
27
29
  let n = t.status?.toUpperCase();
28
30
  n && n in e && (e[n] += 1);
29
31
  }), e;
30
- }, [K]), J = () => {
31
- let e = V?.ggnomadProperties?.[0];
32
+ }, [Z]), $ = () => {
33
+ let e = K?.ggnomadProperties?.[0];
32
34
  if (e) return {
33
35
  type: "PROPERTY",
34
36
  listing: {
@@ -38,34 +40,34 @@ function F() {
38
40
  currency: e.currency
39
41
  }
40
42
  };
41
- let t = V?.ggnomadTours?.[0];
43
+ let t = K?.ggnomadTours?.[0];
42
44
  if (t) return {
43
45
  type: "TOUR",
44
46
  listing: t
45
47
  };
46
- let n = V?.ggnomadEvents?.[0];
48
+ let n = K?.ggnomadEvents?.[0];
47
49
  if (n) return {
48
50
  type: "EVENT",
49
51
  listing: n
50
52
  };
51
- let r = V?.ggnomadActivities?.[0];
53
+ let r = K?.ggnomadActivities?.[0];
52
54
  return r ? {
53
55
  type: "ACTIVITY",
54
56
  listing: r
55
57
  } : null;
56
- }, Y = async () => {
57
- let e = J();
58
+ }, ee = async () => {
59
+ let e = $();
58
60
  if (!e) {
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."));
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."));
60
62
  return;
61
63
  }
62
64
  let { type: t, listing: n } = e;
63
65
  try {
64
- let e = await H({ variables: { input: {
66
+ let e = await q({ variables: { input: {
65
67
  bookingType: t,
66
68
  itemId: n.id,
67
69
  itemName: n.name,
68
- guestName: F("ggnomad.bookings.demo.guestName", "Demo Guest"),
70
+ guestName: R("ggnomad.bookings.demo.guestName", "Demo Guest"),
69
71
  guestEmail: "demo@burdenoff.com",
70
72
  guests: 2,
71
73
  totalPrice: Number(n.price ?? 0),
@@ -73,112 +75,113 @@ function F() {
73
75
  paymentMethod: "card"
74
76
  } } });
75
77
  if (e.error) {
76
- s.error(F("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
78
+ s.error(R("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
77
79
  return;
78
80
  }
79
81
  let r = e.data?.createGgnomadBooking;
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();
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();
81
83
  } catch (e) {
82
- s.error(F("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
84
+ s.error(R("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
83
85
  }
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")
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
91
  } });
90
92
  if (e.error) {
91
- s.error(F("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
93
+ s.error(R("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
92
94
  return;
93
95
  }
94
- s.success(F("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), L.referenceCode), await B();
96
+ s.success(R("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), B.referenceCode), await G();
95
97
  } catch (e) {
96
- s.error(F("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
98
+ s.error(R("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
97
99
  } finally {
98
- R(null);
100
+ V(null);
99
101
  }
100
102
  };
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, {
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
108
  primary: {
107
- label: F("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
108
- onSelect: () => void Y(),
109
- disabled: U,
110
- icon: /* @__PURE__ */ N(p, {})
109
+ label: R("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
110
+ onSelect: () => void ee(),
111
+ disabled: J,
112
+ icon: /* @__PURE__ */ I(h, {})
111
113
  },
112
114
  actions: []
113
115
  }),
114
116
  children: [
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.")
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.")
118
120
  }),
119
- /* @__PURE__ */ N(j, {
121
+ /* @__PURE__ */ I(P, {
120
122
  storageKey: "ggnomad-bookings",
121
123
  steps: [
122
124
  {
123
125
  id: "book-stay",
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")
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")
128
130
  },
129
131
  {
130
132
  id: "book-tour",
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")
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")
135
137
  },
136
138
  {
137
139
  id: "book-event",
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")
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")
142
144
  }
143
145
  ]
144
146
  }),
145
- K.length > 0 && /* @__PURE__ */ N("div", {
147
+ /* @__PURE__ */ I(l, { onRedeemed: () => void G() }),
148
+ Z.length > 0 && /* @__PURE__ */ I("div", {
146
149
  className: "grid gap-3 sm:grid-cols-4",
147
150
  children: [
148
151
  {
149
152
  status: "CONFIRMED",
150
- label: F("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
151
- icon: u
153
+ label: R("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
154
+ icon: f
152
155
  },
153
156
  {
154
157
  status: "CHECKED_IN",
155
- label: F("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
156
- icon: f
158
+ label: R("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
159
+ icon: m
157
160
  },
158
161
  {
159
162
  status: "COMPLETED",
160
- label: F("ggnomad.bookings.pipeline.completed", "Completed"),
161
- icon: m
163
+ label: R("ggnomad.bookings.pipeline.completed", "Completed"),
164
+ icon: _
162
165
  },
163
166
  {
164
167
  status: "CANCELLED",
165
- label: F("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
166
- icon: h
168
+ label: R("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
169
+ icon: v
167
170
  }
168
171
  ].map((e) => {
169
- let t = e.icon, n = q[e.status];
170
- return /* @__PURE__ */ P("div", {
172
+ let t = e.icon, n = Q[e.status];
173
+ return /* @__PURE__ */ L("div", {
171
174
  className: "flex flex-col items-center gap-2 rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-center",
172
175
  children: [
173
- /* @__PURE__ */ N("div", {
176
+ /* @__PURE__ */ I("div", {
174
177
  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" })
178
+ children: /* @__PURE__ */ I(t, { className: "size-4" })
176
179
  }),
177
- /* @__PURE__ */ N("p", {
180
+ /* @__PURE__ */ I("p", {
178
181
  className: "text-xs font-medium text-text-secondary",
179
182
  children: e.label
180
183
  }),
181
- /* @__PURE__ */ N("p", {
184
+ /* @__PURE__ */ I("p", {
182
185
  className: "text-lg font-semibold tabular-nums text-text-primary",
183
186
  children: n
184
187
  })
@@ -186,63 +189,80 @@ function F() {
186
189
  }, e.status);
187
190
  })
188
191
  }),
189
- K.length === 0 ? /* @__PURE__ */ N(A, {
192
+ Z.length === 0 ? /* @__PURE__ */ I(N, {
190
193
  illustration: "empty-data",
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, {
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, {
194
197
  treatment: "glass",
195
198
  glow: !0,
196
199
  className: "border-border-seam p-5",
197
- children: [/* @__PURE__ */ N("h2", {
200
+ children: [/* @__PURE__ */ I("h2", {
198
201
  className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
199
- children: F("ggnomad.bookings.activeBookings", "Your bookings")
200
- }), /* @__PURE__ */ N("div", {
202
+ children: R("ggnomad.bookings.activeBookings", "Your bookings")
203
+ }), /* @__PURE__ */ I("div", {
201
204
  className: "grid gap-3 sm:grid-cols-2",
202
- children: K.map((e) => /* @__PURE__ */ N(o, {
205
+ children: Z.map((e) => /* @__PURE__ */ I(o, {
203
206
  title: e.referenceCode,
204
207
  meta: e.itemName,
205
208
  price: `${e.currency} ${Number(e.totalPrice).toFixed(2)}`,
206
- badge: /* @__PURE__ */ N(a, {
209
+ badge: /* @__PURE__ */ I(a, {
207
210
  status: e.status,
208
211
  live: e.status !== "CANCELLED"
209
212
  }),
210
- footer: e.status === "CANCELLED" ? null : /* @__PURE__ */ N(O, {
213
+ footer: e.status === "CANCELLED" ? null : /* @__PURE__ */ I(j, {
211
214
  align: "end",
212
215
  actions: [{
213
- label: F("ggnomad.bookings.actions.cancel", "Cancel booking"),
214
- onSelect: () => R({
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({
215
226
  id: e.id,
216
227
  referenceCode: e.referenceCode
217
228
  }),
218
229
  intent: "destructive",
219
- icon: /* @__PURE__ */ N(h, {})
230
+ icon: /* @__PURE__ */ I(v, {})
220
231
  }]
221
232
  })
222
233
  }, e.id))
223
234
  })]
224
235
  }),
225
- /* @__PURE__ */ N(b, {
226
- open: L !== null,
236
+ /* @__PURE__ */ I(C, {
237
+ open: B !== null,
227
238
  onOpenChange: (e) => {
228
- e || R(null);
239
+ e || V(null);
229
240
  },
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, {
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, {
234
245
  className: "bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover",
235
- disabled: G,
246
+ disabled: X,
236
247
  onClick: (e) => {
237
- e.preventDefault(), X();
248
+ e.preventDefault(), te();
238
249
  },
239
- children: G ? F("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : F("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
250
+ children: X ? R("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : R("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
240
251
  })] })] })
241
- })
252
+ }),
253
+ H ? /* @__PURE__ */ I(c, {
254
+ open: H !== null,
255
+ onOpenChange: (e) => {
256
+ e || U(null);
257
+ },
258
+ bookingId: H.id,
259
+ referenceCode: H.referenceCode,
260
+ bookingStatus: H.status
261
+ }) : null
242
262
  ]
243
263
  });
244
264
  }
245
265
  //#endregion
246
- export { F as BookingsPage };
266
+ export { R as BookingsPage };
247
267
 
248
268
  //# 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, 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 { 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 {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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"}
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-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, 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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-ggnomad",
3
- "version": "2026.719.4",
3
+ "version": "2026.725.1",
4
4
  "description": "Burdenoff GGNomad Microfrontend",
5
5
  "type": "module",
6
6
  "files": [