@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.
- package/dist/ggnomad/components/RedeemTicketPanel.d.ts +13 -0
- package/dist/ggnomad/components/RedeemTicketPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/RedeemTicketPanel.js +69 -0
- package/dist/ggnomad/components/RedeemTicketPanel.js.map +1 -0
- package/dist/ggnomad/components/TicketDialog.d.ts +28 -0
- package/dist/ggnomad/components/TicketDialog.d.ts.map +1 -0
- package/dist/ggnomad/components/TicketDialog.js +126 -0
- package/dist/ggnomad/components/TicketDialog.js.map +1 -0
- package/dist/ggnomad/components/index.d.ts +3 -0
- package/dist/ggnomad/components/index.d.ts.map +1 -1
- package/dist/ggnomad/components/index.js +2 -0
- package/dist/ggnomad/graphql.d.ts +4 -0
- package/dist/ggnomad/graphql.d.ts.map +1 -1
- package/dist/ggnomad/graphql.js +61 -13
- package/dist/ggnomad/graphql.js.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +118 -98
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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"}
|
|
@@ -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"}
|
package/dist/ggnomad/graphql.js
CHANGED
|
@@ -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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
|
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
|
-
`,
|
|
479
|
+
`, D = e`
|
|
432
480
|
mutation RemoveGGNomadTripPlanItem($id: ID!) {
|
|
433
481
|
removeGgnomadTripPlanItem(id: $id)
|
|
434
482
|
}
|
|
435
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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 {
|
|
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":"
|
|
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
|
|
8
|
-
import { CalendarCheck as
|
|
9
|
-
import { useI18n as
|
|
10
|
-
import { useNavigate as
|
|
11
|
-
import { useMutation as
|
|
12
|
-
import { AlertDialog as
|
|
13
|
-
import { jsx as
|
|
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
|
|
16
|
-
let { t:
|
|
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:
|
|
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
|
|
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
|
-
}, [
|
|
31
|
-
let e =
|
|
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 =
|
|
43
|
+
let t = K?.ggnomadTours?.[0];
|
|
42
44
|
if (t) return {
|
|
43
45
|
type: "TOUR",
|
|
44
46
|
listing: t
|
|
45
47
|
};
|
|
46
|
-
let n =
|
|
48
|
+
let n = K?.ggnomadEvents?.[0];
|
|
47
49
|
if (n) return {
|
|
48
50
|
type: "EVENT",
|
|
49
51
|
listing: n
|
|
50
52
|
};
|
|
51
|
-
let r =
|
|
53
|
+
let r = K?.ggnomadActivities?.[0];
|
|
52
54
|
return r ? {
|
|
53
55
|
type: "ACTIVITY",
|
|
54
56
|
listing: r
|
|
55
57
|
} : null;
|
|
56
|
-
},
|
|
57
|
-
let e =
|
|
58
|
+
}, ee = async () => {
|
|
59
|
+
let e = $();
|
|
58
60
|
if (!e) {
|
|
59
|
-
s.warning(
|
|
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
|
|
66
|
+
let e = await q({ variables: { input: {
|
|
65
67
|
bookingType: t,
|
|
66
68
|
itemId: n.id,
|
|
67
69
|
itemName: n.name,
|
|
68
|
-
guestName:
|
|
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(
|
|
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(
|
|
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(
|
|
84
|
+
s.error(R("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
|
|
83
85
|
}
|
|
84
|
-
},
|
|
85
|
-
if (
|
|
86
|
-
let e = await
|
|
87
|
-
id:
|
|
88
|
-
reason:
|
|
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(
|
|
93
|
+
s.error(R("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
|
|
92
94
|
return;
|
|
93
95
|
}
|
|
94
|
-
s.success(
|
|
96
|
+
s.success(R("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), B.referenceCode), await G();
|
|
95
97
|
} catch (e) {
|
|
96
|
-
s.error(
|
|
98
|
+
s.error(R("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
|
|
97
99
|
} finally {
|
|
98
|
-
|
|
100
|
+
V(null);
|
|
99
101
|
}
|
|
100
102
|
};
|
|
101
|
-
return /* @__PURE__ */
|
|
102
|
-
title:
|
|
103
|
-
titleAccent:
|
|
104
|
-
subtitle:
|
|
105
|
-
actions: /* @__PURE__ */
|
|
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:
|
|
108
|
-
onSelect: () => void
|
|
109
|
-
disabled:
|
|
110
|
-
icon: /* @__PURE__ */
|
|
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__ */
|
|
116
|
-
icon: /* @__PURE__ */
|
|
117
|
-
children:
|
|
117
|
+
/* @__PURE__ */ I(F, {
|
|
118
|
+
icon: /* @__PURE__ */ I(f, { className: "size-4" }),
|
|
119
|
+
children: R("ggnomad.bookings.purpose", "Your single place to manage every reservation across travel, stays, tours and events. Track each booking’s status and reference code in real time, and cancel a reservation when plans change — all backed by live API mutations.")
|
|
118
120
|
}),
|
|
119
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ I(P, {
|
|
120
122
|
storageKey: "ggnomad-bookings",
|
|
121
123
|
steps: [
|
|
122
124
|
{
|
|
123
125
|
id: "book-stay",
|
|
124
|
-
label:
|
|
125
|
-
description:
|
|
126
|
-
icon: /* @__PURE__ */
|
|
127
|
-
onClick: () =>
|
|
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:
|
|
132
|
-
description:
|
|
133
|
-
icon: /* @__PURE__ */
|
|
134
|
-
onClick: () =>
|
|
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:
|
|
139
|
-
description:
|
|
140
|
-
icon: /* @__PURE__ */
|
|
141
|
-
onClick: () =>
|
|
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
|
-
|
|
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:
|
|
151
|
-
icon:
|
|
153
|
+
label: R("ggnomad.bookings.pipeline.confirmed", "Confirmed"),
|
|
154
|
+
icon: f
|
|
152
155
|
},
|
|
153
156
|
{
|
|
154
157
|
status: "CHECKED_IN",
|
|
155
|
-
label:
|
|
156
|
-
icon:
|
|
158
|
+
label: R("ggnomad.bookings.pipeline.checkedIn", "Checked-in"),
|
|
159
|
+
icon: m
|
|
157
160
|
},
|
|
158
161
|
{
|
|
159
162
|
status: "COMPLETED",
|
|
160
|
-
label:
|
|
161
|
-
icon:
|
|
163
|
+
label: R("ggnomad.bookings.pipeline.completed", "Completed"),
|
|
164
|
+
icon: _
|
|
162
165
|
},
|
|
163
166
|
{
|
|
164
167
|
status: "CANCELLED",
|
|
165
|
-
label:
|
|
166
|
-
icon:
|
|
168
|
+
label: R("ggnomad.bookings.pipeline.cancelled", "Cancelled"),
|
|
169
|
+
icon: v
|
|
167
170
|
}
|
|
168
171
|
].map((e) => {
|
|
169
|
-
let t = e.icon, n =
|
|
170
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
178
|
+
children: /* @__PURE__ */ I(t, { className: "size-4" })
|
|
176
179
|
}),
|
|
177
|
-
/* @__PURE__ */
|
|
180
|
+
/* @__PURE__ */ I("p", {
|
|
178
181
|
className: "text-xs font-medium text-text-secondary",
|
|
179
182
|
children: e.label
|
|
180
183
|
}),
|
|
181
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
192
|
+
Z.length === 0 ? /* @__PURE__ */ I(N, {
|
|
190
193
|
illustration: "empty-data",
|
|
191
|
-
title:
|
|
192
|
-
description:
|
|
193
|
-
}) : /* @__PURE__ */
|
|
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__ */
|
|
200
|
+
children: [/* @__PURE__ */ I("h2", {
|
|
198
201
|
className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
|
|
199
|
-
children:
|
|
200
|
-
}), /* @__PURE__ */
|
|
202
|
+
children: R("ggnomad.bookings.activeBookings", "Your bookings")
|
|
203
|
+
}), /* @__PURE__ */ I("div", {
|
|
201
204
|
className: "grid gap-3 sm:grid-cols-2",
|
|
202
|
-
children:
|
|
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__ */
|
|
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__ */
|
|
213
|
+
footer: e.status === "CANCELLED" ? null : /* @__PURE__ */ I(j, {
|
|
211
214
|
align: "end",
|
|
212
215
|
actions: [{
|
|
213
|
-
label:
|
|
214
|
-
onSelect: () =>
|
|
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__ */
|
|
230
|
+
icon: /* @__PURE__ */ I(v, {})
|
|
220
231
|
}]
|
|
221
232
|
})
|
|
222
233
|
}, e.id))
|
|
223
234
|
})]
|
|
224
235
|
}),
|
|
225
|
-
/* @__PURE__ */
|
|
226
|
-
open:
|
|
236
|
+
/* @__PURE__ */ I(C, {
|
|
237
|
+
open: B !== null,
|
|
227
238
|
onOpenChange: (e) => {
|
|
228
|
-
e ||
|
|
239
|
+
e || V(null);
|
|
229
240
|
},
|
|
230
|
-
children: /* @__PURE__ */
|
|
231
|
-
disabled:
|
|
232
|
-
children:
|
|
233
|
-
}), /* @__PURE__ */
|
|
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:
|
|
246
|
+
disabled: X,
|
|
236
247
|
onClick: (e) => {
|
|
237
|
-
e.preventDefault(),
|
|
248
|
+
e.preventDefault(), te();
|
|
238
249
|
},
|
|
239
|
-
children:
|
|
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 {
|
|
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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"}
|