@burdenoff/microfe-ggnomad 2026.624.1 → 2026.704.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/ggnomad/components/DetailRow.js +18 -0
  2. package/dist/ggnomad/components/DetailRow.js.map +1 -0
  3. package/dist/ggnomad/components/EntityCard.js +50 -0
  4. package/dist/ggnomad/components/EntityCard.js.map +1 -0
  5. package/dist/ggnomad/components/SectionCard.js +21 -0
  6. package/dist/ggnomad/components/SectionCard.js.map +1 -0
  7. package/dist/ggnomad/components/StatCard.js +27 -0
  8. package/dist/ggnomad/components/StatCard.js.map +1 -0
  9. package/dist/ggnomad/components/StatusBadge.js +65 -0
  10. package/dist/ggnomad/components/StatusBadge.js.map +1 -0
  11. package/dist/ggnomad/components/index.js +5 -0
  12. package/dist/ggnomad/graphql.js +61 -17
  13. package/dist/ggnomad/graphql.js.map +1 -1
  14. package/dist/ggnomad/pages/ActivityDetailsPage.js +84 -37
  15. package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
  16. package/dist/ggnomad/pages/AnalyticsPage.js +111 -55
  17. package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
  18. package/dist/ggnomad/pages/BookingsPage.js +183 -61
  19. package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
  20. package/dist/ggnomad/pages/DashboardPage.js +132 -107
  21. package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
  22. package/dist/ggnomad/pages/EntertainmentPage.js +40 -33
  23. package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
  24. package/dist/ggnomad/pages/EventDetailsPage.js +86 -39
  25. package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
  26. package/dist/ggnomad/pages/FavoritesPage.js +110 -33
  27. package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
  28. package/dist/ggnomad/pages/HospitalityPage.js +45 -42
  29. package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
  30. package/dist/ggnomad/pages/PageShell.js +33 -11
  31. package/dist/ggnomad/pages/PageShell.js.map +1 -1
  32. package/dist/ggnomad/pages/PropertyDetailsPage.js +95 -45
  33. package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
  34. package/dist/ggnomad/pages/ProviderPage.js +65 -29
  35. package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
  36. package/dist/ggnomad/pages/SearchPage.js +57 -48
  37. package/dist/ggnomad/pages/SearchPage.js.map +1 -1
  38. package/dist/ggnomad/pages/TourDetailsPage.js +86 -39
  39. package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
  40. package/dist/ggnomad/pages/TourismPage.js +64 -63
  41. package/dist/ggnomad/pages/TourismPage.js.map +1 -1
  42. package/dist/ggnomad/pages/TravelPage.js +43 -27
  43. package/dist/ggnomad/pages/TravelPage.js.map +1 -1
  44. package/dist/ggnomad/utils/cn.js +10 -0
  45. package/dist/ggnomad/utils/cn.js.map +1 -0
  46. package/dist/ggnomad/utils/notify.js +36 -0
  47. package/dist/ggnomad/utils/notify.js.map +1 -0
  48. package/package.json +3 -2
@@ -1,64 +1,120 @@
1
1
  import { GGNOMAD_OVERVIEW_QUERY as e } from "../graphql.js";
2
2
  import { PageShell as t } from "./PageShell.js";
3
- import { useQuery as n } from "@apollo/client/react";
4
- import { jsx as r, jsxs as i } from "react/jsx-runtime";
3
+ import { StatCard as n } from "../components/StatCard.js";
4
+ import { SectionCard as r } from "../components/SectionCard.js";
5
+ import "../components/index.js";
6
+ import { useMemo as i } from "react";
7
+ import { BarChart3 as a, CalendarCheck as o, DollarSign as s, Percent as c, Users as l } from "lucide-react";
8
+ import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
+ import { useQuery as d } from "@apollo/client/react";
10
+ import { EmphasisPanel as f, PagePurpose as p } from "@burdenoff/fe-libs/ui";
11
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
5
12
  //#region src/ggnomad/pages/AnalyticsPage.tsx
6
- function a() {
7
- let { data: a } = n(e), o = a?.ggnomadAnalytics;
8
- return /* @__PURE__ */ i(t, {
9
- title: "Analytics",
10
- subtitle: "GGNomad performance and growth analytics",
11
- children: [/* @__PURE__ */ i("div", {
12
- className: "grid gap-4 md:grid-cols-4",
13
- children: [
14
- /* @__PURE__ */ i("div", {
15
- className: "rounded-lg border p-4",
16
- children: [/* @__PURE__ */ r("p", {
17
- className: "text-sm text-text-secondary",
18
- children: "Revenue"
19
- }), /* @__PURE__ */ i("p", {
20
- className: "text-xl font-semibold",
21
- children: ["$", Number(o?.totalRevenue ?? 0).toLocaleString()]
22
- })]
23
- }),
24
- /* @__PURE__ */ i("div", {
25
- className: "rounded-lg border p-4",
26
- children: [/* @__PURE__ */ r("p", {
27
- className: "text-sm text-text-secondary",
28
- children: "Bookings"
29
- }), /* @__PURE__ */ r("p", {
30
- className: "text-xl font-semibold",
31
- children: o?.totalBookings ?? 0
32
- })]
33
- }),
34
- /* @__PURE__ */ i("div", {
35
- className: "rounded-lg border p-4",
36
- children: [/* @__PURE__ */ r("p", {
37
- className: "text-sm text-text-secondary",
38
- children: "Users"
39
- }), /* @__PURE__ */ r("p", {
40
- className: "text-xl font-semibold",
41
- children: o?.totalUsers ?? 0
42
- })]
43
- }),
44
- /* @__PURE__ */ i("div", {
45
- className: "rounded-lg border p-4",
46
- children: [/* @__PURE__ */ r("p", {
47
- className: "text-sm text-text-secondary",
48
- children: "Conversion"
49
- }), /* @__PURE__ */ i("p", {
50
- className: "text-xl font-semibold",
51
- children: [Number(o?.conversionRate ?? 0).toFixed(2), "%"]
52
- })]
13
+ var g = [
14
+ "var(--color-chart-1)",
15
+ "var(--color-chart-2)",
16
+ "var(--color-chart-3)",
17
+ "var(--color-chart-4)",
18
+ "var(--color-chart-5)"
19
+ ], _ = (e) => e.replace(/[_-]+/g, " ").trim().replace(/\b\w/g, (e) => e.toUpperCase()) || e;
20
+ function v() {
21
+ let { t: v } = u(), { data: y } = d(e), b = y?.ggnomadAnalytics, x = i(() => {
22
+ let e = b?.revenueByCategory ?? {};
23
+ return Object.entries(e).map(([e, t]) => ({
24
+ label: _(e),
25
+ value: Number(t)
26
+ })).filter((e) => Number.isFinite(e.value) && e.value > 0).sort((e, t) => t.value - e.value);
27
+ }, [b?.revenueByCategory]), S = x.length > 0 ? x[0].value : 0;
28
+ return /* @__PURE__ */ h(t, {
29
+ title: v("ggnomad.analytics.title", "Analytics"),
30
+ titleAccent: v("ggnomad.analytics.title.accent", "Analytics"),
31
+ subtitle: v("ggnomad.analytics.subtitle", "GGNomad performance and growth analytics"),
32
+ children: [
33
+ /* @__PURE__ */ m(p, {
34
+ icon: /* @__PURE__ */ m(a, { className: "size-4" }),
35
+ children: v("ggnomad.analytics.purpose", "Understand how the marketplace is performing — total revenue, bookings, active users and conversion rate at a glance, with a category breakdown to see which domains (stays, tours, events) are driving growth.")
36
+ }),
37
+ /* @__PURE__ */ h(f, { children: [/* @__PURE__ */ m("p", {
38
+ className: "text-sm text-text-secondary",
39
+ children: v("ggnomad.analytics.totalRevenue", "Total revenue")
40
+ }), /* @__PURE__ */ h("p", {
41
+ className: "mt-1 text-3xl font-semibold tabular-nums text-text-primary",
42
+ children: ["$", Number(b?.totalRevenue ?? 0).toLocaleString()]
43
+ })] }),
44
+ /* @__PURE__ */ h("section", {
45
+ className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-4",
46
+ children: [
47
+ /* @__PURE__ */ m(n, {
48
+ hero: !0,
49
+ label: v("ggnomad.analytics.conversion", "Conversion"),
50
+ value: `${Number(b?.conversionRate ?? 0).toFixed(2)}%`,
51
+ icon: /* @__PURE__ */ m(c, {})
52
+ }),
53
+ /* @__PURE__ */ m(n, {
54
+ label: v("ggnomad.analytics.revenue", "Revenue"),
55
+ value: `$${Number(b?.totalRevenue ?? 0).toLocaleString()}`,
56
+ icon: /* @__PURE__ */ m(s, {})
57
+ }),
58
+ /* @__PURE__ */ m(n, {
59
+ label: v("ggnomad.analytics.bookings", "Bookings"),
60
+ value: Number(b?.totalBookings ?? 0).toLocaleString(),
61
+ icon: /* @__PURE__ */ m(o, {})
62
+ }),
63
+ /* @__PURE__ */ m(n, {
64
+ label: v("ggnomad.analytics.users", "Users"),
65
+ value: Number(b?.totalUsers ?? 0).toLocaleString(),
66
+ icon: /* @__PURE__ */ m(l, {})
67
+ })
68
+ ]
69
+ }),
70
+ /* @__PURE__ */ m(r, {
71
+ title: v("ggnomad.analytics.revenueByCategory", "Revenue by category"),
72
+ children: x.length === 0 ? /* @__PURE__ */ h("div", {
73
+ className: "flex flex-col items-center gap-2 py-8 text-center",
74
+ children: [
75
+ /* @__PURE__ */ m("span", {
76
+ className: "flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary",
77
+ children: /* @__PURE__ */ m(a, { className: "size-5" })
78
+ }),
79
+ /* @__PURE__ */ m("p", {
80
+ className: "text-sm font-medium text-text-primary",
81
+ children: v("ggnomad.analytics.empty.title", "No analytics yet")
82
+ }),
83
+ /* @__PURE__ */ m("p", {
84
+ className: "max-w-sm text-sm text-text-secondary",
85
+ children: v("ggnomad.analytics.empty.description", "Revenue by category appears here as bookings come in across stays, tours and events.")
86
+ })
87
+ ]
88
+ }) : /* @__PURE__ */ m("ul", {
89
+ className: "flex flex-col gap-4",
90
+ children: x.map((e, t) => {
91
+ let n = S > 0 ? Math.max(e.value / S * 100, 2) : 0;
92
+ return /* @__PURE__ */ h("li", { children: [/* @__PURE__ */ h("div", {
93
+ className: "flex items-baseline justify-between gap-3",
94
+ children: [/* @__PURE__ */ m("span", {
95
+ className: "truncate text-sm font-medium text-text-primary",
96
+ children: e.label
97
+ }), /* @__PURE__ */ h("span", {
98
+ className: "flex-shrink-0 text-sm tabular-nums text-text-secondary",
99
+ children: ["$", e.value.toLocaleString()]
100
+ })]
101
+ }), /* @__PURE__ */ m("div", {
102
+ className: "mt-1.5 h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken",
103
+ children: /* @__PURE__ */ m("div", {
104
+ className: "h-full rounded-full transition-[width] duration-500 ease-out",
105
+ style: {
106
+ width: `${n}%`,
107
+ backgroundColor: g[t % g.length]
108
+ }
109
+ })
110
+ })] }, e.label);
111
+ })
53
112
  })
54
- ]
55
- }), /* @__PURE__ */ r("pre", {
56
- className: "overflow-auto rounded-lg border p-4 text-xs",
57
- children: JSON.stringify(o?.revenueByCategory ?? {}, null, 2)
58
- })]
113
+ })
114
+ ]
59
115
  });
60
116
  }
61
117
  //#endregion
62
- export { a as AnalyticsPage };
118
+ export { v as AnalyticsPage };
63
119
 
64
120
  //# sourceMappingURL=AnalyticsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/ggnomad/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\n\nimport { GGNOMAD_OVERVIEW_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\n\nexport function AnalyticsPage() {\n const { data } = useQuery<{\n ggnomadAnalytics: {\n totalRevenue: number;\n totalBookings: number;\n totalUsers: number;\n conversionRate: number;\n revenueByCategory: Record<string, unknown>;\n };\n }>(GGNOMAD_OVERVIEW_QUERY);\n const analytics = data?.ggnomadAnalytics;\n\n return (\n <PageShell title=\"Analytics\" subtitle=\"GGNomad performance and growth analytics\">\n <div className=\"grid gap-4 md:grid-cols-4\">\n <div className=\"rounded-lg border p-4\">\n <p className=\"text-sm text-text-secondary\">Revenue</p>\n <p className=\"text-xl font-semibold\">\n ${Number(analytics?.totalRevenue ?? 0).toLocaleString()}\n </p>\n </div>\n <div className=\"rounded-lg border p-4\">\n <p className=\"text-sm text-text-secondary\">Bookings</p>\n <p className=\"text-xl font-semibold\">{analytics?.totalBookings ?? 0}</p>\n </div>\n <div className=\"rounded-lg border p-4\">\n <p className=\"text-sm text-text-secondary\">Users</p>\n <p className=\"text-xl font-semibold\">{analytics?.totalUsers ?? 0}</p>\n </div>\n <div className=\"rounded-lg border p-4\">\n <p className=\"text-sm text-text-secondary\">Conversion</p>\n <p className=\"text-xl font-semibold\">\n {Number(analytics?.conversionRate ?? 0).toFixed(2)}%\n </p>\n </div>\n </div>\n <pre className=\"overflow-auto rounded-lg border p-4 text-xs\">\n {JSON.stringify(analytics?.revenueByCategory ?? {}, null, 2)}\n </pre>\n </PageShell>\n );\n}\n"],"mappings":";;;;;AAKA,SAAgB,IAAgB;CAC9B,IAAM,EAAE,YAAS,EAQd,EAAuB,EACpB,IAAY,GAAM;AAExB,QACE,kBAAC,GAAD;EAAW,OAAM;EAAY,UAAS;YAAtC,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAAW,CAAA,EACtD,kBAAC,KAAD;MAAG,WAAU;gBAAb,CAAqC,KACjC,OAAO,GAAW,gBAAgB,EAAE,CAAC,gBAAgB,CACrD;QACA;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAAY,CAAA,EACvD,kBAAC,KAAD;MAAG,WAAU;gBAAyB,GAAW,iBAAiB;MAAM,CAAA,CACpE;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAAS,CAAA,EACpD,kBAAC,KAAD;MAAG,WAAU;gBAAyB,GAAW,cAAc;MAAM,CAAA,CACjE;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAAc,CAAA,EACzD,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACG,OAAO,GAAW,kBAAkB,EAAE,CAAC,QAAQ,EAAE,EAAC,IACjD;QACA;;IACF;MACN,kBAAC,OAAD;GAAK,WAAU;aACZ,KAAK,UAAU,GAAW,qBAAqB,EAAE,EAAE,MAAM,EAAE;GACxD,CAAA,CACI"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/ggnomad/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { BarChart3, CalendarCheck, DollarSign, Percent, Users } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_OVERVIEW_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatCard } from '../components';\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\n\nconst humanizeCategory = (key: string): string =>\n key\n .replace(/[_-]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase()) || key;\n\nexport function AnalyticsPage() {\n const { t } = useI18n();\n const { data } = useQuery(\n GGNOMAD_OVERVIEW_QUERY as TypedDocumentNode<\n {\n ggnomadAnalytics: {\n totalRevenue: number;\n totalBookings: number;\n totalUsers: number;\n conversionRate: number;\n revenueByCategory: Record<string, unknown>;\n };\n },\n Record<string, never>\n >\n );\n const analytics = data?.ggnomadAnalytics;\n\n const categories = useMemo(() => {\n const raw = analytics?.revenueByCategory ?? {};\n return Object.entries(raw)\n .map(([key, value]) => ({ label: humanizeCategory(key), value: Number(value) }))\n .filter((entry) => Number.isFinite(entry.value) && entry.value > 0)\n .sort((a, b) => b.value - a.value);\n }, [analytics?.revenueByCategory]);\n\n const maxCategory = categories.length > 0 ? categories[0].value : 0;\n\n return (\n <PageShell\n title={t('ggnomad.analytics.title', 'Analytics')}\n titleAccent={t('ggnomad.analytics.title.accent', 'Analytics')}\n subtitle={t('ggnomad.analytics.subtitle', 'GGNomad performance and growth analytics')}\n >\n <PagePurpose icon={<BarChart3 className=\"size-4\" />}>\n {t(\n 'ggnomad.analytics.purpose',\n 'Understand how the marketplace is performing — total revenue, bookings, active users and conversion rate at a glance, with a category breakdown to see which domains (stays, tours, events) are driving growth.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.analytics.totalRevenue', 'Total revenue')}\n </p>\n <p className=\"mt-1 text-3xl font-semibold tabular-nums text-text-primary\">\n ${Number(analytics?.totalRevenue ?? 0).toLocaleString()}\n </p>\n </EmphasisPanel>\n\n <section className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n hero\n label={t('ggnomad.analytics.conversion', 'Conversion')}\n value={`${Number(analytics?.conversionRate ?? 0).toFixed(2)}%`}\n icon={<Percent />}\n />\n <StatCard\n label={t('ggnomad.analytics.revenue', 'Revenue')}\n value={`$${Number(analytics?.totalRevenue ?? 0).toLocaleString()}`}\n icon={<DollarSign />}\n />\n <StatCard\n label={t('ggnomad.analytics.bookings', 'Bookings')}\n value={Number(analytics?.totalBookings ?? 0).toLocaleString()}\n icon={<CalendarCheck />}\n />\n <StatCard\n label={t('ggnomad.analytics.users', 'Users')}\n value={Number(analytics?.totalUsers ?? 0).toLocaleString()}\n icon={<Users />}\n />\n </section>\n\n <SectionCard title={t('ggnomad.analytics.revenueByCategory', 'Revenue by category')}>\n {categories.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <span className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary\">\n <BarChart3 className=\"size-5\" />\n </span>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.analytics.empty.title', 'No analytics yet')}\n </p>\n <p className=\"max-w-sm text-sm text-text-secondary\">\n {t(\n 'ggnomad.analytics.empty.description',\n 'Revenue by category appears here as bookings come in across stays, tours and events.'\n )}\n </p>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {categories.map((category, index) => {\n const pct = maxCategory > 0 ? Math.max((category.value / maxCategory) * 100, 2) : 0;\n return (\n <li key={category.label}>\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {category.label}\n </span>\n <span className=\"flex-shrink-0 text-sm tabular-nums text-text-secondary\">\n ${category.value.toLocaleString()}\n </span>\n </div>\n <div className=\"mt-1.5 h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full transition-[width] duration-500 ease-out\"\n style={{\n width: `${pct}%`,\n backgroundColor: CHART_COLORS[index % CHART_COLORS.length],\n }}\n />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n </PageShell>\n );\n}\n"],"mappings":"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"}
@@ -1,71 +1,193 @@
1
- import { CANCEL_GGNOMAD_BOOKING_MUTATION as e, CREATE_GGNOMAD_BOOKING_MUTATION as t, GGNOMAD_BOOKINGS_QUERY as n } from "../graphql.js";
2
- import { PageShell as r } from "./PageShell.js";
3
- import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
- import { useMutation as a, useQuery as o } from "@apollo/client/react";
5
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
1
+ import { CANCEL_GGNOMAD_BOOKING_MUTATION as e, CREATE_GGNOMAD_BOOKING_MUTATION as t, GGNOMAD_BOOKABLE_SAMPLE_QUERY as n, GGNOMAD_BOOKINGS_QUERY as r } from "../graphql.js";
2
+ import { PageShell as i } from "./PageShell.js";
3
+ import { StatusBadge as a } from "../components/StatusBadge.js";
4
+ import { EntityCard as o } from "../components/EntityCard.js";
5
+ import "../components/index.js";
6
+ import { notify as s } from "../utils/notify.js";
7
+ import { useState as c } from "react";
8
+ import { CalendarCheck as l, Compass as u, Hotel as d, Plus as f, Ticket as p, XCircle as m } from "lucide-react";
9
+ import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
+ import { useNavigate as g } from "react-router-dom";
11
+ import { useMutation as _, useQuery as v } from "@apollo/client/react";
12
+ import { AlertDialog as y, AlertDialogAction as b, AlertDialogCancel as x, AlertDialogContent as S, AlertDialogDescription as C, AlertDialogFooter as w, AlertDialogHeader as T, AlertDialogTitle as E, CTAOverflowMenu as D, GlassCard as O, IllustratedEmptyState as k, NextSteps as A, PagePurpose as j } from "@burdenoff/fe-libs/ui";
13
+ import { jsx as M, jsxs as N } from "react/jsx-runtime";
6
14
  //#region src/ggnomad/pages/BookingsPage.tsx
7
- function l() {
8
- let { t: l } = i(), { data: u, refetch: d } = o(n, { variables: {
15
+ function P() {
16
+ let { t: P } = h(), F = g(), [I, L] = c(null), { data: R, refetch: z } = v(r, { variables: {
9
17
  first: 20,
10
18
  skip: 0
11
- } }), [f, { loading: p }] = a(t, { onCompleted: () => void d() }), [m] = a(e, { onCompleted: () => void d() }), h = u?.ggnomadBookings ?? [];
12
- return /* @__PURE__ */ c(r, {
13
- title: l("ggnomad.bookings.title", "Bookings"),
14
- subtitle: l("ggnomad.bookings.subtitle", "End-to-end booking operations with real API mutations"),
15
- children: [/* @__PURE__ */ s("div", {
16
- className: "flex gap-2",
17
- children: /* @__PURE__ */ s("button", {
18
- className: "rounded-md border px-3 py-2 text-sm",
19
- disabled: p,
20
- onClick: () => {
21
- f({ variables: { input: {
22
- bookingType: "PROPERTY",
23
- itemId: "demo-property",
24
- itemName: "Demo Property",
25
- guestName: "Demo Guest",
26
- guestEmail: "demo@burdenoff.com",
27
- guestPhone: "+910000000000",
28
- guests: 2,
29
- totalPrice: 299,
30
- currency: "USD",
31
- paymentMethod: "card"
32
- } } });
19
+ } }), { data: B } = v(n), [V, { loading: H }] = _(t), [U, { loading: W }] = _(e), G = R?.ggnomadBookings ?? [], K = () => {
20
+ let e = B?.ggnomadProperties?.[0];
21
+ if (e) return {
22
+ type: "PROPERTY",
23
+ listing: {
24
+ id: e.id,
25
+ name: e.name,
26
+ price: e.pricePerNight,
27
+ currency: e.currency
28
+ }
29
+ };
30
+ let t = B?.ggnomadTours?.[0];
31
+ if (t) return {
32
+ type: "TOUR",
33
+ listing: t
34
+ };
35
+ let n = B?.ggnomadEvents?.[0];
36
+ if (n) return {
37
+ type: "EVENT",
38
+ listing: n
39
+ };
40
+ let r = B?.ggnomadActivities?.[0];
41
+ return r ? {
42
+ type: "ACTIVITY",
43
+ listing: r
44
+ } : null;
45
+ }, q = async () => {
46
+ let e = K();
47
+ if (!e) {
48
+ s.warning(P("ggnomad.bookings.toast.noListingsTitle", "No listings available to book yet"), P("ggnomad.bookings.toast.noListingsBody", "Add a property, tour, event or activity first, then create a booking against it."));
49
+ return;
50
+ }
51
+ let { type: t, listing: n } = e;
52
+ try {
53
+ let e = await V({ variables: { input: {
54
+ bookingType: t,
55
+ itemId: n.id,
56
+ itemName: n.name,
57
+ guestName: P("ggnomad.bookings.demo.guestName", "Demo Guest"),
58
+ guestEmail: "demo@burdenoff.com",
59
+ guests: 2,
60
+ totalPrice: Number(n.price ?? 0),
61
+ currency: n.currency ?? "USD",
62
+ paymentMethod: "card"
63
+ } } });
64
+ if (e.error) {
65
+ s.error(P("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e.error.message);
66
+ return;
67
+ }
68
+ let r = e.data?.createGgnomadBooking;
69
+ s.success(P("ggnomad.bookings.toast.createSuccessTitle", "Booking confirmed"), r ? P("ggnomad.bookings.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : n.name), await z();
70
+ } catch (e) {
71
+ s.error(P("ggnomad.bookings.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
72
+ }
73
+ }, J = async () => {
74
+ if (I) try {
75
+ let e = await U({ variables: {
76
+ id: I.id,
77
+ reason: P("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
78
+ } });
79
+ if (e.error) {
80
+ s.error(P("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e.error.message);
81
+ return;
82
+ }
83
+ s.success(P("ggnomad.bookings.toast.cancelSuccessTitle", "Booking cancelled"), I.referenceCode), await z();
84
+ } catch (e) {
85
+ s.error(P("ggnomad.bookings.toast.cancelErrorTitle", "Could not cancel booking"), e instanceof Error ? e.message : String(e));
86
+ } finally {
87
+ L(null);
88
+ }
89
+ };
90
+ return /* @__PURE__ */ N(i, {
91
+ title: P("ggnomad.bookings.title", "Bookings"),
92
+ titleAccent: P("ggnomad.bookings.title.accent", "Bookings"),
93
+ subtitle: P("ggnomad.bookings.subtitle", "End-to-end booking operations with real API mutations"),
94
+ actions: /* @__PURE__ */ M(D, {
95
+ primary: {
96
+ label: P("ggnomad.bookings.actions.createDemoBooking", "Create demo booking"),
97
+ onSelect: () => void q(),
98
+ disabled: H,
99
+ icon: /* @__PURE__ */ M(f, {})
100
+ },
101
+ actions: []
102
+ }),
103
+ children: [
104
+ /* @__PURE__ */ M(j, {
105
+ icon: /* @__PURE__ */ M(l, { className: "size-4" }),
106
+ children: P("ggnomad.bookings.purpose", "Your single place to manage every reservation across travel, stays, tours and events. Track each booking’s status and reference code in real time, and cancel a reservation when plans change — all backed by live API mutations.")
107
+ }),
108
+ /* @__PURE__ */ M(A, {
109
+ storageKey: "ggnomad-bookings",
110
+ steps: [
111
+ {
112
+ id: "book-stay",
113
+ label: P("ggnomad.bookings.next.stay", "Book a place to stay"),
114
+ description: P("ggnomad.bookings.next.stayDesc", "Browse hospitality listings and reserve a property."),
115
+ icon: /* @__PURE__ */ M(d, { className: "size-4" }),
116
+ onClick: () => F("../hospitality")
117
+ },
118
+ {
119
+ id: "book-tour",
120
+ label: P("ggnomad.bookings.next.tour", "Add a tour or activity"),
121
+ description: P("ggnomad.bookings.next.tourDesc", "Pick a curated experience from Tourism."),
122
+ icon: /* @__PURE__ */ M(u, { className: "size-4" }),
123
+ onClick: () => F("../tourism")
124
+ },
125
+ {
126
+ id: "book-event",
127
+ label: P("ggnomad.bookings.next.event", "Grab event tickets"),
128
+ description: P("ggnomad.bookings.next.eventDesc", "Find live events to round out the itinerary."),
129
+ icon: /* @__PURE__ */ M(p, { className: "size-4" }),
130
+ onClick: () => F("../entertainment")
131
+ }
132
+ ]
133
+ }),
134
+ G.length === 0 ? /* @__PURE__ */ M(k, {
135
+ illustration: "empty-data",
136
+ title: P("ggnomad.bookings.emptyTitle", "No bookings yet"),
137
+ description: P("ggnomad.bookings.emptyDescription", "Create your first booking to see it tracked here in real time.")
138
+ }) : /* @__PURE__ */ N(O, {
139
+ treatment: "glass",
140
+ glow: !0,
141
+ className: "border-border-seam p-5",
142
+ children: [/* @__PURE__ */ M("h2", {
143
+ className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
144
+ children: P("ggnomad.bookings.activeBookings", "Your bookings")
145
+ }), /* @__PURE__ */ M("div", {
146
+ className: "grid gap-3 sm:grid-cols-2",
147
+ children: G.map((e) => /* @__PURE__ */ M(o, {
148
+ title: e.referenceCode,
149
+ meta: e.itemName,
150
+ price: `${e.currency} ${Number(e.totalPrice).toFixed(2)}`,
151
+ badge: /* @__PURE__ */ M(a, {
152
+ status: e.status,
153
+ live: e.status !== "CANCELLED"
154
+ }),
155
+ footer: e.status === "CANCELLED" ? null : /* @__PURE__ */ M(D, {
156
+ align: "end",
157
+ actions: [{
158
+ label: P("ggnomad.bookings.actions.cancel", "Cancel booking"),
159
+ onSelect: () => L({
160
+ id: e.id,
161
+ referenceCode: e.referenceCode
162
+ }),
163
+ intent: "destructive",
164
+ icon: /* @__PURE__ */ M(m, {})
165
+ }]
166
+ })
167
+ }, e.id))
168
+ })]
169
+ }),
170
+ /* @__PURE__ */ M(y, {
171
+ open: I !== null,
172
+ onOpenChange: (e) => {
173
+ e || L(null);
33
174
  },
34
- type: "button",
35
- children: l("ggnomad.bookings.actions.createDemoBooking", "Create demo booking")
175
+ children: /* @__PURE__ */ N(S, { children: [/* @__PURE__ */ N(T, { children: [/* @__PURE__ */ M(E, { children: P("ggnomad.bookings.cancelDialog.title", "Cancel this booking?") }), /* @__PURE__ */ M(C, { children: P("ggnomad.bookings.cancelDialog.body", "This releases reservation {{ref}} and cannot be undone.").replace("{{ref}}", I?.referenceCode ?? "") })] }), /* @__PURE__ */ N(w, { children: [/* @__PURE__ */ M(x, {
176
+ disabled: W,
177
+ children: P("ggnomad.bookings.cancelDialog.keep", "Keep booking")
178
+ }), /* @__PURE__ */ M(b, {
179
+ className: "bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover",
180
+ disabled: W,
181
+ onClick: (e) => {
182
+ e.preventDefault(), J();
183
+ },
184
+ children: W ? P("ggnomad.bookings.cancelDialog.cancelling", "Cancelling…") : P("ggnomad.bookings.cancelDialog.confirm", "Cancel booking")
185
+ })] })] })
36
186
  })
37
- }), /* @__PURE__ */ s("div", {
38
- className: "space-y-2",
39
- children: h.map((e) => /* @__PURE__ */ c("div", {
40
- className: "flex items-center justify-between rounded-lg border p-3 text-sm",
41
- children: [/* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("p", {
42
- className: "font-medium",
43
- children: e.referenceCode
44
- }), /* @__PURE__ */ s("p", { children: e.itemName })] }), /* @__PURE__ */ c("div", {
45
- className: "flex items-center gap-2",
46
- children: [/* @__PURE__ */ c("span", { children: [
47
- e.status,
48
- " - ",
49
- e.currency,
50
- " ",
51
- Number(e.totalPrice).toFixed(2)
52
- ] }), e.status === "CANCELLED" ? null : /* @__PURE__ */ s("button", {
53
- className: "rounded border px-2 py-1",
54
- onClick: () => {
55
- m({ variables: {
56
- id: e.id,
57
- reason: l("ggnomad.bookings.actions.cancelReason", "Cancelled via UI")
58
- } });
59
- },
60
- type: "button",
61
- children: l("ggnomad.bookings.actions.cancel", "Cancel")
62
- })]
63
- })]
64
- }, e.id))
65
- })]
187
+ ]
66
188
  });
67
189
  }
68
190
  //#endregion
69
- export { l as BookingsPage };
191
+ export { P as BookingsPage };
70
192
 
71
193
  //# sourceMappingURL=BookingsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BookingsPage.js","names":[],"sources":["../../../src/ggnomad/pages/BookingsPage.tsx"],"sourcesContent":["import { useMutation, useQuery } from '@apollo/client/react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport {\n CANCEL_GGNOMAD_BOOKING_MUTATION,\n CREATE_GGNOMAD_BOOKING_MUTATION,\n GGNOMAD_BOOKINGS_QUERY,\n} from '../graphql';\nimport { PageShell } from './PageShell';\n\nexport function BookingsPage() {\n const { t } = useI18n();\n const { data, refetch } = useQuery<{\n ggnomadBookings: Array<{\n id: string;\n referenceCode: string;\n itemName: string;\n status: string;\n totalPrice: number;\n currency: string;\n }>;\n }>(GGNOMAD_BOOKINGS_QUERY, { variables: { first: 20, skip: 0 } });\n const [createBooking, { loading: creating }] = useMutation(CREATE_GGNOMAD_BOOKING_MUTATION, {\n onCompleted: () => void refetch(),\n });\n const [cancelBooking] = useMutation(CANCEL_GGNOMAD_BOOKING_MUTATION, {\n onCompleted: () => void refetch(),\n });\n\n const bookings = data?.ggnomadBookings ?? [];\n\n return (\n <PageShell\n title={t('ggnomad.bookings.title', 'Bookings')}\n subtitle={t(\n 'ggnomad.bookings.subtitle',\n 'End-to-end booking operations with real API mutations'\n )}\n >\n <div className=\"flex gap-2\">\n <button\n className=\"rounded-md border px-3 py-2 text-sm\"\n disabled={creating}\n onClick={() => {\n void createBooking({\n variables: {\n input: {\n bookingType: 'PROPERTY',\n itemId: 'demo-property',\n itemName: 'Demo Property',\n guestName: 'Demo Guest',\n guestEmail: 'demo@burdenoff.com',\n guestPhone: '+910000000000',\n guests: 2,\n totalPrice: 299,\n currency: 'USD',\n paymentMethod: 'card',\n },\n },\n });\n }}\n type=\"button\"\n >\n {t('ggnomad.bookings.actions.createDemoBooking', 'Create demo booking')}\n </button>\n </div>\n\n <div className=\"space-y-2\">\n {bookings.map(\n (booking: {\n id: string;\n referenceCode: string;\n itemName: string;\n status: string;\n totalPrice: number;\n currency: string;\n }) => (\n <div\n key={booking.id}\n className=\"flex items-center justify-between rounded-lg border p-3 text-sm\"\n >\n <div>\n <p className=\"font-medium\">{booking.referenceCode}</p>\n <p>{booking.itemName}</p>\n </div>\n <div className=\"flex items-center gap-2\">\n <span>\n {booking.status} - {booking.currency} {Number(booking.totalPrice).toFixed(2)}\n </span>\n {booking.status !== 'CANCELLED' ? (\n <button\n className=\"rounded border px-2 py-1\"\n onClick={() => {\n void cancelBooking({\n variables: {\n id: booking.id,\n reason: t('ggnomad.bookings.actions.cancelReason', 'Cancelled via UI'),\n },\n });\n }}\n type=\"button\"\n >\n {t('ggnomad.bookings.actions.cancel', 'Cancel')}\n </button>\n ) : null}\n </div>\n </div>\n )\n )}\n </div>\n </PageShell>\n );\n}\n"],"mappings":";;;;;;AAUA,SAAgB,IAAe;CAC7B,IAAM,EAAE,SAAM,GAAS,EACjB,EAAE,SAAM,eAAY,EASvB,GAAwB,EAAE,WAAW;EAAE,OAAO;EAAI,MAAM;EAAG,EAAE,CAAC,EAC3D,CAAC,GAAe,EAAE,SAAS,OAAc,EAAY,GAAiC,EAC1F,mBAAmB,KAAK,GAAS,EAClC,CAAC,EACI,CAAC,KAAiB,EAAY,GAAiC,EACnE,mBAAmB,KAAK,GAAS,EAClC,CAAC,EAEI,IAAW,GAAM,mBAAmB,EAAE;AAE5C,QACE,kBAAC,GAAD;EACE,OAAO,EAAE,0BAA0B,WAAW;EAC9C,UAAU,EACR,6BACA,wDACD;YALH,CAOE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,UAAD;IACE,WAAU;IACV,UAAU;IACV,eAAe;AACR,OAAc,EACjB,WAAW,EACT,OAAO;MACL,aAAa;MACb,QAAQ;MACR,UAAU;MACV,WAAW;MACX,YAAY;MACZ,YAAY;MACZ,QAAQ;MACR,YAAY;MACZ,UAAU;MACV,eAAe;MAChB,EACF,EACF,CAAC;;IAEJ,MAAK;cAEJ,EAAE,8CAA8C,sBAAsB;IAChE,CAAA;GACL,CAAA,EAEN,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAS,KACP,MAQC,kBAAC,OAAD;IAEE,WAAU;cAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAAe,EAAQ;KAAkB,CAAA,EACtD,kBAAC,KAAD,EAAA,UAAI,EAAQ,UAAa,CAAA,CACrB,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UAAA;MACG,EAAQ;MAAO;MAAI,EAAQ;MAAS;MAAE,OAAO,EAAQ,WAAW,CAAC,QAAQ,EAAE;MACvE,EAAA,CAAA,EACN,EAAQ,WAAW,cAehB,OAdF,kBAAC,UAAD;MACE,WAAU;MACV,eAAe;AACR,SAAc,EACjB,WAAW;QACT,IAAI,EAAQ;QACZ,QAAQ,EAAE,yCAAyC,mBAAmB;QACvE,EACF,CAAC;;MAEJ,MAAK;gBAEJ,EAAE,mCAAmC,SAAS;MACxC,CAAA,CAEP;OACF;MA5BC,EAAQ,GA4BT,CAET;GACG,CAAA,CACI"}
1
+ {"version":3,"file":"BookingsPage.js","names":[],"sources":["../../../src/ggnomad/pages/BookingsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { CalendarCheck, Compass, Hotel, Plus, Ticket, XCircle } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n CTAOverflowMenu,\n GlassCard,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n CANCEL_GGNOMAD_BOOKING_MUTATION,\n CREATE_GGNOMAD_BOOKING_MUTATION,\n GGNOMAD_BOOKABLE_SAMPLE_QUERY,\n GGNOMAD_BOOKINGS_QUERY,\n} from '../graphql';\nimport { PageShell } from './PageShell';\nimport { EntityCard, StatusBadge } from '../components';\nimport { notify } from '../utils/notify';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype PricedListing = { id: string; name: string; price: number; currency: string };\n\ntype CancelTarget = { id: string; referenceCode: string };\n\nexport function BookingsPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const [cancelTarget, setCancelTarget] = useState<CancelTarget | null>(null);\n\n const { data, refetch } = useQuery(\n GGNOMAD_BOOKINGS_QUERY as TypedDocumentNode<\n {\n ggnomadBookings: Array<{\n id: string;\n referenceCode: string;\n itemName: string;\n status: string;\n totalPrice: number;\n currency: string;\n }>;\n },\n { first: number; skip: number }\n >,\n { variables: { first: 20, skip: 0 } }\n );\n\n // One real listing of each type so the demo booking targets an id that\n // actually exists in the workspace (the backend rejects synthetic ids).\n const { data: sampleData } = useQuery(\n GGNOMAD_BOOKABLE_SAMPLE_QUERY as TypedDocumentNode<\n {\n ggnomadProperties: Array<{\n id: string;\n name: string;\n pricePerNight: number;\n currency: string;\n }>;\n ggnomadTours: Array<{ id: string; name: string; price: number; currency: string }>;\n ggnomadEvents: Array<{ id: string; name: string; price: number; currency: string }>;\n ggnomadActivities: Array<{ id: string; name: string; price: number; currency: string }>;\n },\n Record<string, never>\n >\n );\n\n const [createBooking, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { createGgnomadBooking: { id: string; referenceCode: string; itemName: string } },\n {\n input: {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n guestName: string;\n guestEmail: string;\n guests: number;\n totalPrice: number;\n currency: string;\n paymentMethod: string;\n };\n }\n >\n );\n\n const [cancelBooking, { loading: cancelling }] = useMutation(\n CANCEL_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { cancelGgnomadBooking: { id: string; status: string } },\n { id: string; reason: string }\n >\n );\n\n const bookings = data?.ggnomadBookings ?? [];\n\n /** First available real listing, in priority order, mapped to a booking type. */\n const resolveDemoTarget = (): { type: BookingType; listing: PricedListing } | null => {\n const property = sampleData?.ggnomadProperties?.[0];\n if (property) {\n return {\n type: 'PROPERTY',\n listing: {\n id: property.id,\n name: property.name,\n price: property.pricePerNight,\n currency: property.currency,\n },\n };\n }\n const tour = sampleData?.ggnomadTours?.[0];\n if (tour) return { type: 'TOUR', listing: tour };\n const event = sampleData?.ggnomadEvents?.[0];\n if (event) return { type: 'EVENT', listing: event };\n const activity = sampleData?.ggnomadActivities?.[0];\n if (activity) return { type: 'ACTIVITY', listing: activity };\n return null;\n };\n\n const handleCreateDemoBooking = async () => {\n const target = resolveDemoTarget();\n if (!target) {\n notify.warning(\n t('ggnomad.bookings.toast.noListingsTitle', 'No listings available to book yet'),\n t(\n 'ggnomad.bookings.toast.noListingsBody',\n 'Add a property, tour, event or activity first, then create a booking against it.'\n )\n );\n return;\n }\n const { type, listing } = target;\n try {\n const result = await createBooking({\n variables: {\n input: {\n bookingType: type,\n itemId: listing.id,\n itemName: listing.name,\n guestName: t('ggnomad.bookings.demo.guestName', 'Demo Guest'),\n guestEmail: 'demo@burdenoff.com',\n guests: 2,\n totalPrice: Number(listing.price ?? 0),\n currency: listing.currency ?? 'USD',\n paymentMethod: 'card',\n },\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.bookings.toast.createErrorTitle', 'Could not create booking'),\n result.error.message\n );\n return;\n }\n const created = result.data?.createGgnomadBooking;\n notify.success(\n t('ggnomad.bookings.toast.createSuccessTitle', 'Booking confirmed'),\n created\n ? t('ggnomad.bookings.toast.createSuccessBody', '{{ref}} · {{item}}')\n .replace('{{ref}}', created.referenceCode)\n .replace('{{item}}', created.itemName)\n : listing.name\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.bookings.toast.createErrorTitle', 'Could not create booking'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const confirmCancel = async () => {\n if (!cancelTarget) return;\n try {\n const result = await cancelBooking({\n variables: {\n id: cancelTarget.id,\n reason: t('ggnomad.bookings.actions.cancelReason', 'Cancelled via UI'),\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.bookings.toast.cancelErrorTitle', 'Could not cancel booking'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.bookings.toast.cancelSuccessTitle', 'Booking cancelled'),\n cancelTarget.referenceCode\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.bookings.toast.cancelErrorTitle', 'Could not cancel booking'),\n err instanceof Error ? err.message : String(err)\n );\n } finally {\n setCancelTarget(null);\n }\n };\n\n return (\n <PageShell\n title={t('ggnomad.bookings.title', 'Bookings')}\n titleAccent={t('ggnomad.bookings.title.accent', 'Bookings')}\n subtitle={t(\n 'ggnomad.bookings.subtitle',\n 'End-to-end booking operations with real API mutations'\n )}\n actions={\n <CTAOverflowMenu\n primary={{\n label: t('ggnomad.bookings.actions.createDemoBooking', 'Create demo booking'),\n onSelect: () => void handleCreateDemoBooking(),\n disabled: creating,\n icon: <Plus />,\n }}\n actions={[]}\n />\n }\n >\n <PagePurpose icon={<CalendarCheck className=\"size-4\" />}>\n {t(\n 'ggnomad.bookings.purpose',\n 'Your single place to manage every reservation across travel, stays, tours and events. Track each booking’s status and reference code in real time, and cancel a reservation when plans change — all backed by live API mutations.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"ggnomad-bookings\"\n steps={[\n {\n id: 'book-stay',\n label: t('ggnomad.bookings.next.stay', 'Book a place to stay'),\n description: t(\n 'ggnomad.bookings.next.stayDesc',\n 'Browse hospitality listings and reserve a property.'\n ),\n icon: <Hotel className=\"size-4\" />,\n onClick: () => navigate('../hospitality'),\n },\n {\n id: 'book-tour',\n label: t('ggnomad.bookings.next.tour', 'Add a tour or activity'),\n description: t(\n 'ggnomad.bookings.next.tourDesc',\n 'Pick a curated experience from Tourism.'\n ),\n icon: <Compass className=\"size-4\" />,\n onClick: () => navigate('../tourism'),\n },\n {\n id: 'book-event',\n label: t('ggnomad.bookings.next.event', 'Grab event tickets'),\n description: t(\n 'ggnomad.bookings.next.eventDesc',\n 'Find live events to round out the itinerary.'\n ),\n icon: <Ticket className=\"size-4\" />,\n onClick: () => navigate('../entertainment'),\n },\n ]}\n />\n\n {bookings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.bookings.emptyTitle', 'No bookings yet')}\n description={t(\n 'ggnomad.bookings.emptyDescription',\n 'Create your first booking to see it tracked here in real time.'\n )}\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.bookings.activeBookings', 'Your bookings')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {bookings.map((booking) => (\n <EntityCard\n key={booking.id}\n title={booking.referenceCode}\n meta={booking.itemName}\n price={`${booking.currency} ${Number(booking.totalPrice).toFixed(2)}`}\n badge={\n <StatusBadge status={booking.status} live={booking.status !== 'CANCELLED'} />\n }\n footer={\n booking.status !== 'CANCELLED' ? (\n <CTAOverflowMenu\n align=\"end\"\n actions={[\n {\n label: t('ggnomad.bookings.actions.cancel', 'Cancel booking'),\n onSelect: () =>\n setCancelTarget({\n id: booking.id,\n referenceCode: booking.referenceCode,\n }),\n intent: 'destructive',\n icon: <XCircle />,\n },\n ]}\n />\n ) : null\n }\n />\n ))}\n </div>\n </GlassCard>\n )}\n\n <AlertDialog\n open={cancelTarget !== null}\n onOpenChange={(open) => {\n if (!open) setCancelTarget(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {t('ggnomad.bookings.cancelDialog.title', 'Cancel this booking?')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t(\n 'ggnomad.bookings.cancelDialog.body',\n 'This releases reservation {{ref}} and cannot be undone.'\n ).replace('{{ref}}', cancelTarget?.referenceCode ?? '')}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={cancelling}>\n {t('ggnomad.bookings.cancelDialog.keep', 'Keep booking')}\n </AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-action-danger-bg text-action-danger-text hover:bg-action-danger-bgHover\"\n disabled={cancelling}\n onClick={(event) => {\n event.preventDefault();\n void confirmCancel();\n }}\n >\n {cancelling\n ? t('ggnomad.bookings.cancelDialog.cancelling', 'Cancelling…')\n : t('ggnomad.bookings.cancelDialog.confirm', 'Cancel booking')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </PageShell>\n );\n}\n"],"mappings":"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"}