@burdenoff/microfe-ggnomad 2026.801.1 → 2026.827.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/ggnomad/components/AvailabilityPanel.d.ts +15 -0
  2. package/dist/ggnomad/components/AvailabilityPanel.d.ts.map +1 -0
  3. package/dist/ggnomad/components/AvailabilityPanel.js +91 -0
  4. package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -0
  5. package/dist/ggnomad/components/BookingPanel.d.ts +1 -3
  6. package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
  7. package/dist/ggnomad/components/BookingPanel.js +91 -82
  8. package/dist/ggnomad/components/BookingPanel.js.map +1 -1
  9. package/dist/ggnomad/components/EcoAccessPanel.d.ts +17 -0
  10. package/dist/ggnomad/components/EcoAccessPanel.d.ts.map +1 -0
  11. package/dist/ggnomad/components/EcoAccessPanel.js +68 -0
  12. package/dist/ggnomad/components/EcoAccessPanel.js.map +1 -0
  13. package/dist/ggnomad/components/FactGrid.d.ts +18 -0
  14. package/dist/ggnomad/components/FactGrid.d.ts.map +1 -0
  15. package/dist/ggnomad/components/FactGrid.js +29 -0
  16. package/dist/ggnomad/components/FactGrid.js.map +1 -0
  17. package/dist/ggnomad/components/GroupTripPanel.d.ts +22 -0
  18. package/dist/ggnomad/components/GroupTripPanel.d.ts.map +1 -0
  19. package/dist/ggnomad/components/GroupTripPanel.js +350 -0
  20. package/dist/ggnomad/components/GroupTripPanel.js.map +1 -0
  21. package/dist/ggnomad/components/ListingFilters.d.ts +29 -0
  22. package/dist/ggnomad/components/ListingFilters.d.ts.map +1 -0
  23. package/dist/ggnomad/components/ListingFilters.js +164 -0
  24. package/dist/ggnomad/components/ListingFilters.js.map +1 -0
  25. package/dist/ggnomad/components/PhotoGallery.d.ts +15 -0
  26. package/dist/ggnomad/components/PhotoGallery.d.ts.map +1 -0
  27. package/dist/ggnomad/components/PhotoGallery.js +54 -0
  28. package/dist/ggnomad/components/PhotoGallery.js.map +1 -0
  29. package/dist/ggnomad/components/ProviderCard.d.ts +19 -0
  30. package/dist/ggnomad/components/ProviderCard.d.ts.map +1 -0
  31. package/dist/ggnomad/components/ProviderCard.js +66 -0
  32. package/dist/ggnomad/components/ProviderCard.js.map +1 -0
  33. package/dist/ggnomad/components/RatingSummary.d.ts +14 -0
  34. package/dist/ggnomad/components/RatingSummary.d.ts.map +1 -0
  35. package/dist/ggnomad/components/RatingSummary.js +32 -0
  36. package/dist/ggnomad/components/RatingSummary.js.map +1 -0
  37. package/dist/ggnomad/components/TagChips.d.ts +16 -0
  38. package/dist/ggnomad/components/TagChips.d.ts.map +1 -0
  39. package/dist/ggnomad/components/TagChips.js +20 -0
  40. package/dist/ggnomad/components/TagChips.js.map +1 -0
  41. package/dist/ggnomad/components/index.d.ts +13 -0
  42. package/dist/ggnomad/components/index.d.ts.map +1 -1
  43. package/dist/ggnomad/components/index.js +9 -0
  44. package/dist/ggnomad/graphql.d.ts +10 -0
  45. package/dist/ggnomad/graphql.d.ts.map +1 -1
  46. package/dist/ggnomad/graphql.js +308 -20
  47. package/dist/ggnomad/graphql.js.map +1 -1
  48. package/dist/ggnomad/pages/ActivityDetailsPage.d.ts.map +1 -1
  49. package/dist/ggnomad/pages/ActivityDetailsPage.js +174 -86
  50. package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
  51. package/dist/ggnomad/pages/AnalyticsPage.d.ts.map +1 -1
  52. package/dist/ggnomad/pages/AnalyticsPage.js +109 -77
  53. package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
  54. package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
  55. package/dist/ggnomad/pages/BookingsPage.js +188 -137
  56. package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
  57. package/dist/ggnomad/pages/DashboardPage.d.ts.map +1 -1
  58. package/dist/ggnomad/pages/DashboardPage.js +245 -128
  59. package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
  60. package/dist/ggnomad/pages/EntertainmentPage.d.ts.map +1 -1
  61. package/dist/ggnomad/pages/EntertainmentPage.js +115 -53
  62. package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
  63. package/dist/ggnomad/pages/EventDetailsPage.d.ts.map +1 -1
  64. package/dist/ggnomad/pages/EventDetailsPage.js +170 -87
  65. package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
  66. package/dist/ggnomad/pages/FavoritesPage.d.ts.map +1 -1
  67. package/dist/ggnomad/pages/FavoritesPage.js +201 -84
  68. package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
  69. package/dist/ggnomad/pages/HospitalityPage.d.ts.map +1 -1
  70. package/dist/ggnomad/pages/HospitalityPage.js +99 -56
  71. package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
  72. package/dist/ggnomad/pages/PropertyDetailsPage.d.ts.map +1 -1
  73. package/dist/ggnomad/pages/PropertyDetailsPage.js +177 -93
  74. package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
  75. package/dist/ggnomad/pages/ProviderPage.d.ts.map +1 -1
  76. package/dist/ggnomad/pages/ProviderPage.js +112 -63
  77. package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
  78. package/dist/ggnomad/pages/SearchPage.d.ts.map +1 -1
  79. package/dist/ggnomad/pages/SearchPage.js +236 -65
  80. package/dist/ggnomad/pages/SearchPage.js.map +1 -1
  81. package/dist/ggnomad/pages/TourDetailsPage.d.ts.map +1 -1
  82. package/dist/ggnomad/pages/TourDetailsPage.js +171 -86
  83. package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
  84. package/dist/ggnomad/pages/TourismPage.d.ts.map +1 -1
  85. package/dist/ggnomad/pages/TourismPage.js +108 -54
  86. package/dist/ggnomad/pages/TourismPage.js.map +1 -1
  87. package/dist/ggnomad/pages/TravelPage.d.ts.map +1 -1
  88. package/dist/ggnomad/pages/TravelPage.js +406 -43
  89. package/dist/ggnomad/pages/TravelPage.js.map +1 -1
  90. package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
  91. package/dist/ggnomad/pages/TripPlanDetailPage.js +93 -70
  92. package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
  93. package/dist/ggnomad/utils/format.d.ts +47 -0
  94. package/dist/ggnomad/utils/format.d.ts.map +1 -0
  95. package/dist/ggnomad/utils/format.js +125 -0
  96. package/dist/ggnomad/utils/format.js.map +1 -0
  97. package/dist/stories/BookingPanel.stories.d.ts.map +1 -1
  98. package/package.json +2 -2
@@ -0,0 +1,15 @@
1
+ type GgnomadListingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';
2
+ interface AvailabilityPanelProps {
3
+ listingId: string;
4
+ listingType: GgnomadListingType;
5
+ }
6
+ /**
7
+ * BOFF-4361 — the listing's published availability slots (`ggnomadBookingSlots`),
8
+ * from today forward. Read-only: it reports the capacity the operator published and
9
+ * how much of it is already taken; reserving a slot stays with the booking panel.
10
+ * The toggle switches the query's own `onlyAvailable` argument, so "sold out" rows
11
+ * are real records, never a placeholder.
12
+ */
13
+ export declare function AvailabilityPanel({ listingId, listingType }: AvailabilityPanelProps): import("react").JSX.Element | null;
14
+ export {};
15
+ //# sourceMappingURL=AvailabilityPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AvailabilityPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/AvailabilityPanel.tsx"],"names":[],"mappings":"AAUA,KAAK,kBAAkB,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAYrE,UAAU,sBAAsB;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAID;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,sBAAsB,sCAmInF"}
@@ -0,0 +1,91 @@
1
+ import { GGNOMAD_BOOKING_SLOTS_QUERY as e } from "../graphql.js";
2
+ import { formatDate as t, formatTimeOnly as n } from "../utils/format.js";
3
+ import { SectionCard as r } from "./SectionCard.js";
4
+ import { useMemo as i, useState as a } from "react";
5
+ import { CalendarDays as o } from "lucide-react";
6
+ import { useI18n as s } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
+ import { useQuery as c } from "@apollo/client/react";
8
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
9
+ //#region src/ggnomad/components/AvailabilityPanel.tsx
10
+ var d = 30;
11
+ function f({ listingId: f, listingType: p }) {
12
+ let { t: m } = s(), [h, g] = a(!0), [_, v] = a(!1), { data: y, loading: b, error: x } = c(e, {
13
+ variables: {
14
+ listingId: f,
15
+ listingType: p,
16
+ dateFrom: i(() => (/* @__PURE__ */ new Date()).toISOString(), []),
17
+ onlyAvailable: h,
18
+ limit: d,
19
+ offset: 0
20
+ },
21
+ skip: !f
22
+ }), S = y?.ggnomadBookingSlots ?? [];
23
+ return !_ && !b && !x && S.length === 0 && h ? null : /* @__PURE__ */ l(r, {
24
+ title: m("ggnomad.availability.title", "Availability"),
25
+ action: /* @__PURE__ */ u("label", {
26
+ className: "flex items-center gap-2 text-sm text-text-secondary",
27
+ children: [/* @__PURE__ */ l("input", {
28
+ type: "checkbox",
29
+ checked: !h,
30
+ onChange: (e) => {
31
+ v(!0), g(!e.target.checked);
32
+ },
33
+ className: "size-4 rounded border-border-seam accent-[var(--color-action-primary-bg)]"
34
+ }), m("ggnomad.availability.includeFull", "Include full & closed slots")]
35
+ }),
36
+ children: x ? /* @__PURE__ */ l("p", {
37
+ className: "text-sm text-status-error-text",
38
+ children: m("ggnomad.availability.error", "Could not load availability: {{message}}").replace("{{message}}", x.message)
39
+ }) : b ? /* @__PURE__ */ l("p", {
40
+ className: "text-sm text-text-secondary",
41
+ children: m("ggnomad.availability.loading", "Loading availability…")
42
+ }) : S.length === 0 ? /* @__PURE__ */ l("p", {
43
+ className: "text-sm text-text-secondary",
44
+ children: m("ggnomad.availability.empty", "No upcoming slots published for this listing.")
45
+ }) : /* @__PURE__ */ l("ul", {
46
+ className: "space-y-2",
47
+ children: S.map((e) => {
48
+ let r = t(e.startAt), i = n(e.startAt), a = n(e.endAt), s = e.remainingCapacity <= 0;
49
+ return /* @__PURE__ */ u("li", {
50
+ className: "flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3",
51
+ children: [/* @__PURE__ */ u("div", {
52
+ className: "flex min-w-0 items-center gap-3",
53
+ children: [/* @__PURE__ */ l("span", {
54
+ className: "flex size-8 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary",
55
+ "aria-hidden": "true",
56
+ children: /* @__PURE__ */ l(o, { className: "size-4" })
57
+ }), /* @__PURE__ */ u("div", {
58
+ className: "min-w-0",
59
+ children: [/* @__PURE__ */ l("p", {
60
+ className: "truncate text-sm font-medium text-text-primary",
61
+ children: r ?? e.startAt
62
+ }), i ? /* @__PURE__ */ l("p", {
63
+ className: "text-xs text-text-secondary",
64
+ children: a ? `${i} – ${a}` : i
65
+ }) : null]
66
+ })]
67
+ }), /* @__PURE__ */ u("div", {
68
+ className: "flex items-center gap-3 text-sm",
69
+ children: [/* @__PURE__ */ l("span", {
70
+ className: "tabular-nums text-text-secondary",
71
+ children: m("ggnomad.availability.remaining", "{{left}} of {{total}} left").replace("{{left}}", String(Math.max(0, e.remainingCapacity))).replace("{{total}}", String(e.capacity))
72
+ }), e.isActive ? s ? /* @__PURE__ */ l("span", {
73
+ className: "rounded-full bg-status-error-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-error-text",
74
+ children: m("ggnomad.availability.soldOut", "Sold out")
75
+ }) : /* @__PURE__ */ l("span", {
76
+ className: "rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text",
77
+ children: m("ggnomad.availability.open", "Available")
78
+ }) : /* @__PURE__ */ l("span", {
79
+ className: "rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
80
+ children: m("ggnomad.availability.closed", "Closed")
81
+ })]
82
+ })]
83
+ }, e.id);
84
+ })
85
+ })
86
+ });
87
+ }
88
+ //#endregion
89
+ export { f as AvailabilityPanel };
90
+
91
+ //# sourceMappingURL=AvailabilityPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AvailabilityPanel.js","names":[],"sources":["../../../src/ggnomad/components/AvailabilityPanel.tsx"],"sourcesContent":["import { useMemo, useState } 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 { CalendarDays } from 'lucide-react';\n\nimport { GGNOMAD_BOOKING_SLOTS_QUERY } from '../graphql';\nimport { formatDate, formatTimeOnly } from '../utils/format';\nimport { SectionCard } from './SectionCard';\n\ntype GgnomadListingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ninterface BookingSlot {\n id: string;\n startAt: string;\n endAt?: string | null;\n capacity: number;\n bookedCount: number;\n remainingCapacity: number;\n isActive: boolean;\n}\n\ninterface AvailabilityPanelProps {\n listingId: string;\n listingType: GgnomadListingType;\n}\n\nconst SLOT_LIMIT = 30;\n\n/**\n * BOFF-4361 — the listing's published availability slots (`ggnomadBookingSlots`),\n * from today forward. Read-only: it reports the capacity the operator published and\n * how much of it is already taken; reserving a slot stays with the booking panel.\n * The toggle switches the query's own `onlyAvailable` argument, so \"sold out\" rows\n * are real records, never a placeholder.\n */\nexport function AvailabilityPanel({ listingId, listingType }: AvailabilityPanelProps) {\n const { t } = useI18n();\n const [onlyAvailable, setOnlyAvailable] = useState(true);\n const [touched, setTouched] = useState(false);\n const dateFrom = useMemo(() => new Date().toISOString(), []);\n\n const { data, loading, error } = useQuery(\n GGNOMAD_BOOKING_SLOTS_QUERY as TypedDocumentNode<\n { ggnomadBookingSlots: BookingSlot[] },\n {\n listingId: string;\n listingType: GgnomadListingType;\n dateFrom: string;\n onlyAvailable: boolean;\n limit: number;\n offset: number;\n }\n >,\n {\n variables: {\n listingId,\n listingType,\n dateFrom,\n onlyAvailable,\n limit: SLOT_LIMIT,\n offset: 0,\n },\n skip: !listingId,\n }\n );\n\n const slots = data?.ggnomadBookingSlots ?? [];\n\n // A listing that publishes no slots at all is booked by date, not by slot — the\n // panel stays out of the way rather than showing a permanently empty section.\n // Once the reader has used the toggle it stays put, so switching back to\n // \"available only\" cannot make the control it was just using disappear.\n if (!touched && !loading && !error && slots.length === 0 && onlyAvailable) {\n return null;\n }\n\n return (\n <SectionCard\n title={t('ggnomad.availability.title', 'Availability')}\n action={\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={!onlyAvailable}\n onChange={(event) => {\n setTouched(true);\n setOnlyAvailable(!event.target.checked);\n }}\n className=\"size-4 rounded border-border-seam accent-[var(--color-action-primary-bg)]\"\n />\n {t('ggnomad.availability.includeFull', 'Include full & closed slots')}\n </label>\n }\n >\n {error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('ggnomad.availability.error', 'Could not load availability: {{message}}').replace(\n '{{message}}',\n error.message\n )}\n </p>\n ) : loading ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.availability.loading', 'Loading availability…')}\n </p>\n ) : slots.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.availability.empty', 'No upcoming slots published for this listing.')}\n </p>\n ) : (\n <ul className=\"space-y-2\">\n {slots.map((slot) => {\n const day = formatDate(slot.startAt);\n const startTime = formatTimeOnly(slot.startAt);\n const endTime = formatTimeOnly(slot.endAt);\n const soldOut = slot.remainingCapacity <= 0;\n return (\n <li\n key={slot.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <span\n className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <CalendarDays className=\"size-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {day ?? slot.startAt}\n </p>\n {startTime ? (\n <p className=\"text-xs text-text-secondary\">\n {endTime ? `${startTime} – ${endTime}` : startTime}\n </p>\n ) : null}\n </div>\n </div>\n <div className=\"flex items-center gap-3 text-sm\">\n <span className=\"tabular-nums text-text-secondary\">\n {t('ggnomad.availability.remaining', '{{left}} of {{total}} left')\n .replace('{{left}}', String(Math.max(0, slot.remainingCapacity)))\n .replace('{{total}}', String(slot.capacity))}\n </span>\n {!slot.isActive ? (\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {t('ggnomad.availability.closed', 'Closed')}\n </span>\n ) : soldOut ? (\n <span className=\"rounded-full bg-status-error-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-error-text\">\n {t('ggnomad.availability.soldOut', 'Sold out')}\n </span>\n ) : (\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n {t('ggnomad.availability.open', 'Available')}\n </span>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n );\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAM,IAAa;AASnB,SAAgB,EAAkB,EAAE,cAAW,kBAAuC;CACpF,IAAM,EAAE,SAAM,EAAQ,GAChB,CAAC,GAAe,KAAoB,EAAS,EAAI,GACjD,CAAC,GAAS,KAAc,EAAS,EAAK,GAGtC,EAAE,SAAM,YAAS,aAAU,EAC/B,GAWA;EACE,WAAW;GACT;GACA;GACA,UAlBW,yBAAc,IAAI,KAAK,GAAE,YAAY,GAAG,CAAC,CAkBpD;GACA;GACA,OAAO;GACP,QAAQ;EACV;EACA,MAAM,CAAC;CACT,CACF,GAEM,IAAQ,GAAM,uBAAuB,CAAC;CAU5C,OAJI,CAAC,KAAW,CAAC,KAAW,CAAC,KAAS,EAAM,WAAW,KAAK,IACnD,OAIP,kBAAC,GAAD;EACE,OAAO,EAAE,8BAA8B,cAAc;EACrD,QACE,kBAAC,SAAD;GAAO,WAAU;aAAjB,CACE,kBAAC,SAAD;IACE,MAAK;IACL,SAAS,CAAC;IACV,WAAW,MAAU;KAEnB,AADA,EAAW,EAAI,GACf,EAAiB,CAAC,EAAM,OAAO,OAAO;IACxC;IACA,WAAU;GACX,CAAA,GACA,EAAE,oCAAoC,6BAA6B,CAC/D;;YAGR,IACC,kBAAC,KAAD;GAAG,WAAU;aACV,EAAE,8BAA8B,0CAA0C,EAAE,QAC3E,eACA,EAAM,OACR;EACC,CAAA,IACD,IACF,kBAAC,KAAD;GAAG,WAAU;aACV,EAAE,gCAAgC,uBAAuB;EACzD,CAAA,IACD,EAAM,WAAW,IACnB,kBAAC,KAAD;GAAG,WAAU;aACV,EAAE,8BAA8B,+CAA+C;EAC/E,CAAA,IAEH,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,KAAK,MAAS;IACnB,IAAM,IAAM,EAAW,EAAK,OAAO,GAC7B,IAAY,EAAe,EAAK,OAAO,GACvC,IAAU,EAAe,EAAK,KAAK,GACnC,IAAU,EAAK,qBAAqB;IAC1C,OACE,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OACE,WAAU;OACV,eAAY;iBAEZ,kBAAC,GAAD,EAAc,WAAU,SAAU,CAAA;MAC9B,CAAA,GACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,KAAO,EAAK;OACZ,CAAA,GACF,IACC,kBAAC,KAAD;QAAG,WAAU;kBACV,IAAU,GAAG,EAAU,KAAK,MAAY;OACxC,CAAA,IACD,IACD;QACF;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,kCAAkC,4BAA4B,EAC9D,QAAQ,YAAY,OAAO,KAAK,IAAI,GAAG,EAAK,iBAAiB,CAAC,CAAC,EAC/D,QAAQ,aAAa,OAAO,EAAK,QAAQ,CAAC;MACzC,CAAA,GACJ,EAAK,WAIH,IACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,gCAAgC,UAAU;MACzC,CAAA,IAEN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,6BAA6B,WAAW;MACvC,CAAA,IAVN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,+BAA+B,QAAQ;MACtC,CAAA,CAUL;OACH;OAzCG,EAAK,EAyCR;GAER,CAAC;EACC,CAAA;CAEK,CAAA;AAEjB"}
@@ -5,12 +5,10 @@ type BookingPanelProps = {
5
5
  itemName: string;
6
6
  unitPrice: number;
7
7
  currency: string;
8
- /** For PROPERTY bookings, the number of nights to calculate price */
9
- nights?: number;
10
8
  /** For EVENT bookings, the event date (ISO string) */
11
9
  eventDate?: string;
12
10
  };
13
11
  /** Frosted glass booking panel with type-specific inputs and real createGgnomadBooking mutation. */
14
- export declare function BookingPanel({ bookingType, itemId, itemName, unitPrice, currency, nights, eventDate, }: BookingPanelProps): import("react").JSX.Element;
12
+ export declare function BookingPanel({ bookingType, itemId, itemName, unitPrice, currency, eventDate, }: BookingPanelProps): import("react").JSX.Element;
15
13
  export {};
16
14
  //# sourceMappingURL=BookingPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BookingPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/BookingPanel.tsx"],"names":[],"mappings":"AAcA,KAAK,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE9D,KAAK,iBAAiB,GAAG;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,MAAM,EACN,SAAS,GACV,EAAE,iBAAiB,+BAySnB"}
1
+ {"version":3,"file":"BookingPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/BookingPanel.tsx"],"names":[],"mappings":"AAeA,KAAK,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE9D,KAAK,iBAAiB,GAAG;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,GACV,EAAE,iBAAiB,+BAmUnB"}
@@ -1,48 +1,53 @@
1
1
  import { CREATE_GGNOMAD_BOOKING_MUTATION as e } from "../graphql.js";
2
- import { cn as t } from "../utils/cn.js";
3
- import { useGGNomad as n } from "../providers/GGNomadProvider.js";
4
- import { joinPath as r } from "../utils/navigation.js";
5
- import { notify as i } from "../utils/notify.js";
6
- import { useState as a } from "react";
7
- import { Minus as o, Plus as s } from "lucide-react";
8
- import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
- import { useNavigate as l } from "react-router";
10
- import { useMutation as u } from "@apollo/client/react";
11
- import { Button as d, GlassCard as f, Input as p } from "@burdenoff/fe-libs/ui";
12
- import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
2
+ import { nightsBetween as t } from "../utils/format.js";
3
+ import { cn as n } from "../utils/cn.js";
4
+ import { useGGNomad as r } from "../providers/GGNomadProvider.js";
5
+ import { joinPath as i } from "../utils/navigation.js";
6
+ import { notify as a } from "../utils/notify.js";
7
+ import { useState as o } from "react";
8
+ import { Minus as s, Plus as c } from "lucide-react";
9
+ import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
+ import { useNavigate as u } from "react-router";
11
+ import { useMutation as d } from "@apollo/client/react";
12
+ import { Button as f, GlassCard as p, Input as m } from "@burdenoff/fe-libs/ui";
13
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
13
14
  //#region src/ggnomad/components/BookingPanel.tsx
14
- function _({ bookingType: _, itemId: v, itemName: y, unitPrice: b, currency: x, nights: S, eventDate: C }) {
15
- let { t: w } = c(), T = l(), { basePath: E = "/" } = n(), [D, O] = a(""), [k, A] = a(""), [j, M] = a(""), [N, P] = a(1), [F, { loading: I }] = u(e), L = b * (_ === "PROPERTY" && S ? S : N), R = L * .1, z = L + R, B = async () => {
16
- if (_ === "PROPERTY") {
15
+ function v({ bookingType: v, itemId: y, itemName: b, unitPrice: x, currency: S, eventDate: C }) {
16
+ let { t: w } = l(), T = u(), { basePath: E = "/" } = r(), [D, O] = o(""), [k, A] = o(""), [j, M] = o(""), [N, P] = o(1), [F, { loading: I }] = d(e), L = v === "PROPERTY" ? t(k, j) : 0, R = x * (v === "PROPERTY" ? L : N), z = R * .1, B = R + z, V = async () => {
17
+ if (v === "PROPERTY") {
17
18
  if (!k || !j) {
18
- i.warning(w("ggnomad.booking.validation.missingDates", "Missing dates"), w("ggnomad.booking.validation.propertyDatesRequired", "Please select both check-in and check-out dates."));
19
+ a.warning(w("ggnomad.booking.validation.missingDates", "Missing dates"), w("ggnomad.booking.validation.propertyDatesRequired", "Please select both check-in and check-out dates."));
19
20
  return;
20
21
  }
21
- } else if (_ === "EVENT") {
22
+ if (L < 1) {
23
+ a.warning(w("ggnomad.booking.validation.invalidStayTitle", "Check-out must be after check-in"), w("ggnomad.booking.validation.invalidStayBody", "Pick a check-out date at least one night after check-in."));
24
+ return;
25
+ }
26
+ } else if (v === "EVENT") {
22
27
  if (!C) {
23
- i.warning(w("ggnomad.booking.validation.missingDate", "Missing date"), w("ggnomad.booking.validation.eventDateRequired", "Event date is required for booking."));
28
+ a.warning(w("ggnomad.booking.validation.missingDate", "Missing date"), w("ggnomad.booking.validation.eventDateRequired", "Event date is required for booking."));
24
29
  return;
25
30
  }
26
31
  } else if (!D) {
27
- i.warning(w("ggnomad.booking.validation.missingDate", "Missing date"), w("ggnomad.booking.validation.dateRequired", "Please select a date for your booking."));
32
+ a.warning(w("ggnomad.booking.validation.missingDate", "Missing date"), w("ggnomad.booking.validation.dateRequired", "Please select a date for your booking."));
28
33
  return;
29
34
  }
30
35
  try {
31
36
  let e = {
32
- bookingType: _,
33
- itemId: v,
34
- itemName: y,
37
+ bookingType: v,
38
+ itemId: y,
39
+ itemName: b,
35
40
  guestName: w("ggnomad.booking.defaultGuestName", "Demo Guest"),
36
41
  guestEmail: "demo@burdenoff.com",
37
42
  guests: N,
38
- totalPrice: Math.round(z * 100) / 100,
39
- currency: x,
43
+ totalPrice: Math.round(B * 100) / 100,
44
+ currency: S,
40
45
  paymentMethod: "card"
41
- }, t = await F({ variables: { input: _ === "PROPERTY" ? {
46
+ }, t = await F({ variables: { input: v === "PROPERTY" ? {
42
47
  ...e,
43
48
  checkIn: k,
44
49
  checkOut: j
45
- } : _ === "EVENT" ? {
50
+ } : v === "EVENT" ? {
46
51
  ...e,
47
52
  eventDate: C
48
53
  } : {
@@ -50,170 +55,174 @@ function _({ bookingType: _, itemId: v, itemName: y, unitPrice: b, currency: x,
50
55
  eventDate: D
51
56
  } } });
52
57
  if (t.error) {
53
- i.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"), t.error.message);
58
+ a.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"), t.error.message);
54
59
  return;
55
60
  }
56
61
  let n = t.data?.createGgnomadBooking;
57
- i.success(w("ggnomad.booking.toast.createSuccessTitle", "Booking confirmed"), n ? w("ggnomad.booking.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", n.referenceCode).replace("{{item}}", n.itemName) : y), T(r(E, "bookings"));
62
+ a.success(w("ggnomad.booking.toast.createSuccessTitle", "Booking confirmed"), n ? w("ggnomad.booking.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", n.referenceCode).replace("{{item}}", n.itemName) : b), T(i(E, "bookings"));
58
63
  } catch (e) {
59
- i.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
64
+ a.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
60
65
  }
61
- }, V = "text-sm font-medium text-text-primary", H = "w-full";
62
- return /* @__PURE__ */ g(f, {
66
+ }, H = "text-sm font-medium text-text-primary", U = "w-full";
67
+ return /* @__PURE__ */ _(p, {
63
68
  treatment: "glass",
64
69
  glow: !0,
65
70
  className: "border-border-seam p-5 sticky top-6",
66
71
  children: [
67
- /* @__PURE__ */ h("h2", {
72
+ /* @__PURE__ */ g("h2", {
68
73
  className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
69
- children: _ === "PROPERTY" ? w("ggnomad.booking.title.property", "Book this stay") : _ === "EVENT" ? w("ggnomad.booking.title.event", "Book tickets") : _ === "TOUR" ? w("ggnomad.booking.title.tour", "Book this tour") : w("ggnomad.booking.title.activity", "Book this activity")
74
+ children: v === "PROPERTY" ? w("ggnomad.booking.title.property", "Book this stay") : v === "EVENT" ? w("ggnomad.booking.title.event", "Book tickets") : v === "TOUR" ? w("ggnomad.booking.title.tour", "Book this tour") : w("ggnomad.booking.title.activity", "Book this activity")
70
75
  }),
71
- /* @__PURE__ */ g("div", {
76
+ /* @__PURE__ */ _("div", {
72
77
  className: "space-y-4 mb-6",
73
- children: [_ === "PROPERTY" ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
78
+ children: [v === "PROPERTY" ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
74
79
  htmlFor: "check-in",
75
- className: V,
80
+ className: H,
76
81
  children: w("ggnomad.booking.checkIn", "Check-in")
77
- }), /* @__PURE__ */ h(p, {
82
+ }), /* @__PURE__ */ g(m, {
78
83
  id: "check-in",
79
84
  type: "date",
80
85
  value: k,
81
86
  onChange: (e) => A(e.target.value),
82
- className: t(H, "mt-2"),
87
+ className: n(U, "mt-2"),
83
88
  "aria-label": w("ggnomad.booking.checkIn", "Check-in")
84
- })] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
89
+ })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
85
90
  htmlFor: "check-out",
86
- className: V,
91
+ className: H,
87
92
  children: w("ggnomad.booking.checkOut", "Check-out")
88
- }), /* @__PURE__ */ h(p, {
93
+ }), /* @__PURE__ */ g(m, {
89
94
  id: "check-out",
90
95
  type: "date",
96
+ min: k || void 0,
91
97
  value: j,
92
98
  onChange: (e) => M(e.target.value),
93
- className: t(H, "mt-2"),
99
+ className: n(U, "mt-2"),
94
100
  "aria-label": w("ggnomad.booking.checkOut", "Check-out")
95
- })] })] }) : _ === "EVENT" ? /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
101
+ })] })] }) : v === "EVENT" ? /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
96
102
  htmlFor: "event-date",
97
- className: V,
103
+ className: H,
98
104
  children: w("ggnomad.booking.date", "Date")
99
- }), /* @__PURE__ */ h(p, {
105
+ }), /* @__PURE__ */ g(m, {
100
106
  id: "event-date",
101
107
  type: "date",
102
108
  value: C ? C.split("T")[0] : "",
103
109
  disabled: !0,
104
- className: t(H, "mt-2"),
110
+ className: n(U, "mt-2"),
105
111
  "aria-label": w("ggnomad.booking.date", "Date")
106
- })] }) : /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
112
+ })] }) : /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
107
113
  htmlFor: "booking-date",
108
- className: V,
114
+ className: H,
109
115
  children: w("ggnomad.booking.date", "Date")
110
- }), /* @__PURE__ */ h(p, {
116
+ }), /* @__PURE__ */ g(m, {
111
117
  id: "booking-date",
112
118
  type: "date",
113
119
  value: D,
114
120
  onChange: (e) => O(e.target.value),
115
- className: t(H, "mt-2"),
121
+ className: n(U, "mt-2"),
116
122
  "aria-label": w("ggnomad.booking.date", "Date")
117
- })] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
118
- className: V,
119
- children: _ === "PROPERTY" ? w("ggnomad.booking.guests", "Guests") : _ === "EVENT" ? w("ggnomad.booking.tickets", "Tickets") : w("ggnomad.booking.participants", "Participants")
120
- }), /* @__PURE__ */ g("div", {
123
+ })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
124
+ className: H,
125
+ children: v === "PROPERTY" ? w("ggnomad.booking.guests", "Guests") : v === "EVENT" ? w("ggnomad.booking.tickets", "Tickets") : w("ggnomad.booking.participants", "Participants")
126
+ }), /* @__PURE__ */ _("div", {
121
127
  className: "flex items-center gap-2 mt-2",
122
128
  children: [
123
- /* @__PURE__ */ h(d, {
129
+ /* @__PURE__ */ g(f, {
124
130
  variant: "outline",
125
131
  size: "sm",
126
132
  onClick: () => P(Math.max(1, N - 1)),
127
133
  disabled: N <= 1 || I,
128
134
  "aria-label": w("ggnomad.booking.decreaseQuantity", "Decrease quantity"),
129
135
  className: "flex-shrink-0",
130
- children: /* @__PURE__ */ h(o, { className: "size-4" })
136
+ children: /* @__PURE__ */ g(s, { className: "size-4" })
131
137
  }),
132
- /* @__PURE__ */ h("input", {
138
+ /* @__PURE__ */ g("input", {
133
139
  type: "number",
134
140
  min: 1,
135
141
  value: N,
136
142
  onChange: (e) => P(Math.max(1, parseInt(e.target.value) || 1)),
137
143
  className: "flex-1 h-9 rounded-input border border-border-default bg-bg-surface px-3 py-1 text-center text-sm text-text-primary",
138
- "aria-label": _ === "PROPERTY" ? w("ggnomad.booking.guests", "Guests") : _ === "EVENT" ? w("ggnomad.booking.tickets", "Tickets") : w("ggnomad.booking.participants", "Participants")
144
+ "aria-label": v === "PROPERTY" ? w("ggnomad.booking.guests", "Guests") : v === "EVENT" ? w("ggnomad.booking.tickets", "Tickets") : w("ggnomad.booking.participants", "Participants")
139
145
  }),
140
- /* @__PURE__ */ h(d, {
146
+ /* @__PURE__ */ g(f, {
141
147
  variant: "outline",
142
148
  size: "sm",
143
149
  onClick: () => P(N + 1),
144
150
  disabled: I,
145
151
  "aria-label": w("ggnomad.booking.increaseQuantity", "Increase quantity"),
146
152
  className: "flex-shrink-0",
147
- children: /* @__PURE__ */ h(s, { className: "size-4" })
153
+ children: /* @__PURE__ */ g(c, { className: "size-4" })
148
154
  })
149
155
  ]
150
156
  })] })]
151
157
  }),
152
- /* @__PURE__ */ g("div", {
158
+ v === "PROPERTY" && L < 1 ? /* @__PURE__ */ g("div", {
159
+ className: "mb-6 rounded-md bg-bg-sunken p-3 text-sm text-text-secondary",
160
+ children: w("ggnomad.booking.selectDatesForTotal", "Select check-in and check-out dates to see the total.")
161
+ }) : /* @__PURE__ */ _("div", {
153
162
  className: "space-y-2 mb-6 p-3 rounded-md bg-bg-sunken",
154
163
  children: [
155
- /* @__PURE__ */ g("div", {
164
+ /* @__PURE__ */ _("div", {
156
165
  className: "flex justify-between text-sm",
157
- children: [/* @__PURE__ */ g("span", {
166
+ children: [/* @__PURE__ */ _("span", {
158
167
  className: "text-text-secondary",
159
168
  children: [
160
- x,
169
+ S,
161
170
  " ",
162
- b.toFixed(2),
171
+ x.toFixed(2),
163
172
  " ×",
164
173
  " ",
165
- _ === "PROPERTY" ? `${S ?? 1} ${w("ggnomad.booking.nights", "nights")}` : `${N} ${w("ggnomad.booking.item", "item")}`
174
+ v === "PROPERTY" ? L === 1 ? `1 ${w("ggnomad.booking.night", "night")}` : `${L} ${w("ggnomad.booking.nights", "nights")}` : `${N} ${w("ggnomad.booking.item", "item")}`
166
175
  ]
167
- }), /* @__PURE__ */ g("span", {
176
+ }), /* @__PURE__ */ _("span", {
168
177
  className: "text-text-primary font-medium",
169
178
  children: [
170
- x,
179
+ S,
171
180
  " ",
172
- L.toFixed(2)
181
+ R.toFixed(2)
173
182
  ]
174
183
  })]
175
184
  }),
176
- /* @__PURE__ */ g("div", {
185
+ /* @__PURE__ */ _("div", {
177
186
  className: "flex justify-between text-sm",
178
- children: [/* @__PURE__ */ h("span", {
187
+ children: [/* @__PURE__ */ g("span", {
179
188
  className: "text-text-secondary",
180
189
  children: w("ggnomad.booking.tax", "Tax (10%)")
181
- }), /* @__PURE__ */ g("span", {
190
+ }), /* @__PURE__ */ _("span", {
182
191
  className: "text-text-primary",
183
192
  children: [
184
- x,
193
+ S,
185
194
  " ",
186
- R.toFixed(2)
195
+ z.toFixed(2)
187
196
  ]
188
197
  })]
189
198
  }),
190
- /* @__PURE__ */ g("div", {
199
+ /* @__PURE__ */ _("div", {
191
200
  className: "border-t border-border-seam pt-2 flex justify-between",
192
- children: [/* @__PURE__ */ h("span", {
201
+ children: [/* @__PURE__ */ g("span", {
193
202
  className: "font-semibold text-text-primary",
194
203
  children: w("ggnomad.booking.total", "Total")
195
- }), /* @__PURE__ */ g("span", {
204
+ }), /* @__PURE__ */ _("span", {
196
205
  className: "font-bold text-lg text-text-primary",
197
206
  children: [
198
- x,
207
+ S,
199
208
  " ",
200
- z.toFixed(2)
209
+ B.toFixed(2)
201
210
  ]
202
211
  })]
203
212
  })
204
213
  ]
205
214
  }),
206
- /* @__PURE__ */ h(d, {
207
- onClick: () => void B(),
215
+ /* @__PURE__ */ g(f, {
216
+ onClick: () => void V(),
208
217
  disabled: I,
209
218
  className: "w-full bg-gradient-to-r from-action-primary-bg to-action-success-bg text-white font-semibold",
210
219
  size: "lg",
211
- children: I ? w("ggnomad.booking.button.creating", "Creating booking…") : _ === "PROPERTY" ? w("ggnomad.booking.button.property", "Book this stay") : _ === "EVENT" ? w("ggnomad.booking.button.event", "Book tickets") : _ === "TOUR" ? w("ggnomad.booking.button.tour", "Book this tour") : w("ggnomad.booking.button.activity", "Book this activity")
220
+ children: I ? w("ggnomad.booking.button.creating", "Creating booking…") : v === "PROPERTY" ? w("ggnomad.booking.button.property", "Book this stay") : v === "EVENT" ? w("ggnomad.booking.button.event", "Book tickets") : v === "TOUR" ? w("ggnomad.booking.button.tour", "Book this tour") : w("ggnomad.booking.button.activity", "Book this activity")
212
221
  })
213
222
  ]
214
223
  });
215
224
  }
216
225
  //#endregion
217
- export { _ as BookingPanel };
226
+ export { v as BookingPanel };
218
227
 
219
228
  //# sourceMappingURL=BookingPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BookingPanel.js","names":[],"sources":["../../../src/ggnomad/components/BookingPanel.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 { useNavigate } from 'react-router';\nimport { Minus, Plus } from 'lucide-react';\nimport { Button, GlassCard, Input } from '@burdenoff/fe-libs/ui';\n\nimport { CREATE_GGNOMAD_BOOKING_MUTATION } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { notify } from '../utils/notify';\nimport { cn } from '../utils/cn';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype BookingPanelProps = {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n unitPrice: number;\n currency: string;\n /** For PROPERTY bookings, the number of nights to calculate price */\n nights?: number;\n /** For EVENT bookings, the event date (ISO string) */\n eventDate?: string;\n};\n\n/** Frosted glass booking panel with type-specific inputs and real createGgnomadBooking mutation. */\nexport function BookingPanel({\n bookingType,\n itemId,\n itemName,\n unitPrice,\n currency,\n nights,\n eventDate,\n}: BookingPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useGGNomad();\n\n // For properties: check-in and check-out dates\n // For events/tours/activities: a single date\n const [selectedDate, setSelectedDate] = useState<string>('');\n const [checkInDate, setCheckInDate] = useState<string>('');\n const [checkOutDate, setCheckOutDate] = useState<string>('');\n\n // Quantity: guests (property), participants (tour/activity), tickets (event)\n const [quantity, setQuantity] = useState(1);\n\n type BookingInput = {\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 checkIn?: string;\n checkOut?: string;\n eventDate?: string;\n };\n\n const [createBooking, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { createGgnomadBooking: { id: string; referenceCode: string; itemName: string } },\n { input: BookingInput }\n >\n );\n\n // Calculate total: for properties, multiply by nights; for others, multiply by quantity\n const multiplier = bookingType === 'PROPERTY' && nights ? nights : quantity;\n const subtotal = unitPrice * multiplier;\n const taxAmount = subtotal * 0.1; // 10% tax\n const total = subtotal + taxAmount;\n\n const handleBooking = async () => {\n // Validate required fields\n if (bookingType === 'PROPERTY') {\n if (!checkInDate || !checkOutDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDates', 'Missing dates'),\n t(\n 'ggnomad.booking.validation.propertyDatesRequired',\n 'Please select both check-in and check-out dates.'\n )\n );\n return;\n }\n } else if (bookingType === 'EVENT') {\n if (!eventDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.eventDateRequired', 'Event date is required for booking.')\n );\n return;\n }\n } else {\n if (!selectedDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.dateRequired', 'Please select a date for your booking.')\n );\n return;\n }\n }\n\n try {\n const baseInput = {\n bookingType,\n itemId,\n itemName,\n guestName: t('ggnomad.booking.defaultGuestName', 'Demo Guest'),\n guestEmail: 'demo@burdenoff.com',\n guests: quantity,\n totalPrice: Math.round(total * 100) / 100,\n currency,\n paymentMethod: 'card',\n };\n\n const input: BookingInput =\n bookingType === 'PROPERTY'\n ? { ...baseInput, checkIn: checkInDate, checkOut: checkOutDate }\n : bookingType === 'EVENT'\n ? { ...baseInput, eventDate: eventDate }\n : { ...baseInput, eventDate: selectedDate };\n\n const result = await createBooking({\n variables: { input },\n });\n\n if (result.error) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n result.error.message\n );\n return;\n }\n\n const created = result.data?.createGgnomadBooking;\n notify.success(\n t('ggnomad.booking.toast.createSuccessTitle', 'Booking confirmed'),\n created\n ? t('ggnomad.booking.toast.createSuccessBody', '{{ref}} · {{item}}')\n .replace('{{ref}}', created.referenceCode)\n .replace('{{item}}', created.itemName)\n : itemName\n );\n\n // Navigate to bookings list\n navigate(joinPath(basePath, 'bookings'));\n } catch (err) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const labelClass = 'text-sm font-medium text-text-primary';\n const inputClass = 'w-full';\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5 sticky top-6\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.title.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.title.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.title.tour', 'Book this tour')\n : t('ggnomad.booking.title.activity', 'Book this activity')}\n </h2>\n\n <div className=\"space-y-4 mb-6\">\n {/* Dates section */}\n {bookingType === 'PROPERTY' ? (\n <>\n <div>\n <label htmlFor=\"check-in\" className={labelClass}>\n {t('ggnomad.booking.checkIn', 'Check-in')}\n </label>\n <Input\n id=\"check-in\"\n type=\"date\"\n value={checkInDate}\n onChange={(e) => setCheckInDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkIn', 'Check-in')}\n />\n </div>\n <div>\n <label htmlFor=\"check-out\" className={labelClass}>\n {t('ggnomad.booking.checkOut', 'Check-out')}\n </label>\n <Input\n id=\"check-out\"\n type=\"date\"\n value={checkOutDate}\n onChange={(e) => setCheckOutDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkOut', 'Check-out')}\n />\n </div>\n </>\n ) : bookingType === 'EVENT' ? (\n <div>\n <label htmlFor=\"event-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"event-date\"\n type=\"date\"\n value={eventDate ? eventDate.split('T')[0] : ''}\n disabled\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n ) : (\n <div>\n <label htmlFor=\"booking-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"booking-date\"\n type=\"date\"\n value={selectedDate}\n onChange={(e) => setSelectedDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n )}\n\n {/* Quantity section */}\n <div>\n <label className={labelClass}>\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')}\n </label>\n <div className=\"flex items-center gap-2 mt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(Math.max(1, quantity - 1))}\n disabled={quantity <= 1 || creating}\n aria-label={t('ggnomad.booking.decreaseQuantity', 'Decrease quantity')}\n className=\"flex-shrink-0\"\n >\n <Minus className=\"size-4\" />\n </Button>\n <input\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => setQuantity(Math.max(1, parseInt(e.target.value) || 1))}\n className=\"flex-1 h-9 rounded-input border border-border-default bg-bg-surface px-3 py-1 text-center text-sm text-text-primary\"\n aria-label={\n bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')\n }\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(quantity + 1)}\n disabled={creating}\n aria-label={t('ggnomad.booking.increaseQuantity', 'Increase quantity')}\n className=\"flex-shrink-0\"\n >\n <Plus className=\"size-4\" />\n </Button>\n </div>\n </div>\n </div>\n\n {/* Pricing breakdown */}\n <div className=\"space-y-2 mb-6 p-3 rounded-md bg-bg-sunken\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {currency} {unitPrice.toFixed(2)} ×{' '}\n {bookingType === 'PROPERTY'\n ? `${nights ?? 1} ${t('ggnomad.booking.nights', 'nights')}`\n : `${quantity} ${t('ggnomad.booking.item', 'item')}`}\n </span>\n <span className=\"text-text-primary font-medium\">\n {currency} {subtotal.toFixed(2)}\n </span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">{t('ggnomad.booking.tax', 'Tax (10%)')}</span>\n <span className=\"text-text-primary\">\n {currency} {taxAmount.toFixed(2)}\n </span>\n </div>\n <div className=\"border-t border-border-seam pt-2 flex justify-between\">\n <span className=\"font-semibold text-text-primary\">\n {t('ggnomad.booking.total', 'Total')}\n </span>\n <span className=\"font-bold text-lg text-text-primary\">\n {currency} {total.toFixed(2)}\n </span>\n </div>\n </div>\n\n {/* Book button */}\n <Button\n onClick={() => void handleBooking()}\n disabled={creating}\n className=\"w-full bg-gradient-to-r from-action-primary-bg to-action-success-bg text-white font-semibold\"\n size=\"lg\"\n >\n {creating\n ? t('ggnomad.booking.button.creating', 'Creating booking…')\n : bookingType === 'PROPERTY'\n ? t('ggnomad.booking.button.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.button.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.button.tour', 'Book this tour')\n : t('ggnomad.booking.button.activity', 'Book this activity')}\n </Button>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BookingPanel.js","names":[],"sources":["../../../src/ggnomad/components/BookingPanel.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 { useNavigate } from 'react-router';\nimport { Minus, Plus } from 'lucide-react';\nimport { Button, GlassCard, Input } from '@burdenoff/fe-libs/ui';\n\nimport { CREATE_GGNOMAD_BOOKING_MUTATION } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { notify } from '../utils/notify';\nimport { nightsBetween } from '../utils/format';\nimport { cn } from '../utils/cn';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype BookingPanelProps = {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n unitPrice: number;\n currency: string;\n /** For EVENT bookings, the event date (ISO string) */\n eventDate?: string;\n};\n\n/** Frosted glass booking panel with type-specific inputs and real createGgnomadBooking mutation. */\nexport function BookingPanel({\n bookingType,\n itemId,\n itemName,\n unitPrice,\n currency,\n eventDate,\n}: BookingPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useGGNomad();\n\n // For properties: check-in and check-out dates\n // For events/tours/activities: a single date\n const [selectedDate, setSelectedDate] = useState<string>('');\n const [checkInDate, setCheckInDate] = useState<string>('');\n const [checkOutDate, setCheckOutDate] = useState<string>('');\n\n // Quantity: guests (property), participants (tour/activity), tickets (event)\n const [quantity, setQuantity] = useState(1);\n\n type BookingInput = {\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 checkIn?: string;\n checkOut?: string;\n eventDate?: string;\n };\n\n const [createBooking, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { createGgnomadBooking: { id: string; referenceCode: string; itemName: string } },\n { input: BookingInput }\n >\n );\n\n // A property is priced per night, so the multiplier is the length of the selected\n // stay — derived from the two date inputs above rather than assumed. Every other\n // listing type is priced per ticket / seat / participant.\n const nights = bookingType === 'PROPERTY' ? nightsBetween(checkInDate, checkOutDate) : 0;\n const multiplier = bookingType === 'PROPERTY' ? nights : quantity;\n const subtotal = unitPrice * multiplier;\n const taxAmount = subtotal * 0.1; // 10% tax\n const total = subtotal + taxAmount;\n\n const handleBooking = async () => {\n // Validate required fields\n if (bookingType === 'PROPERTY') {\n if (!checkInDate || !checkOutDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDates', 'Missing dates'),\n t(\n 'ggnomad.booking.validation.propertyDatesRequired',\n 'Please select both check-in and check-out dates.'\n )\n );\n return;\n }\n if (nights < 1) {\n notify.warning(\n t('ggnomad.booking.validation.invalidStayTitle', 'Check-out must be after check-in'),\n t(\n 'ggnomad.booking.validation.invalidStayBody',\n 'Pick a check-out date at least one night after check-in.'\n )\n );\n return;\n }\n } else if (bookingType === 'EVENT') {\n if (!eventDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.eventDateRequired', 'Event date is required for booking.')\n );\n return;\n }\n } else {\n if (!selectedDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.dateRequired', 'Please select a date for your booking.')\n );\n return;\n }\n }\n\n try {\n const baseInput = {\n bookingType,\n itemId,\n itemName,\n guestName: t('ggnomad.booking.defaultGuestName', 'Demo Guest'),\n guestEmail: 'demo@burdenoff.com',\n guests: quantity,\n totalPrice: Math.round(total * 100) / 100,\n currency,\n paymentMethod: 'card',\n };\n\n const input: BookingInput =\n bookingType === 'PROPERTY'\n ? { ...baseInput, checkIn: checkInDate, checkOut: checkOutDate }\n : bookingType === 'EVENT'\n ? { ...baseInput, eventDate: eventDate }\n : { ...baseInput, eventDate: selectedDate };\n\n const result = await createBooking({\n variables: { input },\n });\n\n if (result.error) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n result.error.message\n );\n return;\n }\n\n const created = result.data?.createGgnomadBooking;\n notify.success(\n t('ggnomad.booking.toast.createSuccessTitle', 'Booking confirmed'),\n created\n ? t('ggnomad.booking.toast.createSuccessBody', '{{ref}} · {{item}}')\n .replace('{{ref}}', created.referenceCode)\n .replace('{{item}}', created.itemName)\n : itemName\n );\n\n // Navigate to bookings list\n navigate(joinPath(basePath, 'bookings'));\n } catch (err) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const labelClass = 'text-sm font-medium text-text-primary';\n const inputClass = 'w-full';\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5 sticky top-6\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.title.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.title.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.title.tour', 'Book this tour')\n : t('ggnomad.booking.title.activity', 'Book this activity')}\n </h2>\n\n <div className=\"space-y-4 mb-6\">\n {/* Dates section */}\n {bookingType === 'PROPERTY' ? (\n <>\n <div>\n <label htmlFor=\"check-in\" className={labelClass}>\n {t('ggnomad.booking.checkIn', 'Check-in')}\n </label>\n <Input\n id=\"check-in\"\n type=\"date\"\n value={checkInDate}\n onChange={(e) => setCheckInDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkIn', 'Check-in')}\n />\n </div>\n <div>\n <label htmlFor=\"check-out\" className={labelClass}>\n {t('ggnomad.booking.checkOut', 'Check-out')}\n </label>\n <Input\n id=\"check-out\"\n type=\"date\"\n min={checkInDate || undefined}\n value={checkOutDate}\n onChange={(e) => setCheckOutDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkOut', 'Check-out')}\n />\n </div>\n </>\n ) : bookingType === 'EVENT' ? (\n <div>\n <label htmlFor=\"event-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"event-date\"\n type=\"date\"\n value={eventDate ? eventDate.split('T')[0] : ''}\n disabled\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n ) : (\n <div>\n <label htmlFor=\"booking-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"booking-date\"\n type=\"date\"\n value={selectedDate}\n onChange={(e) => setSelectedDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n )}\n\n {/* Quantity section */}\n <div>\n <label className={labelClass}>\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')}\n </label>\n <div className=\"flex items-center gap-2 mt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(Math.max(1, quantity - 1))}\n disabled={quantity <= 1 || creating}\n aria-label={t('ggnomad.booking.decreaseQuantity', 'Decrease quantity')}\n className=\"flex-shrink-0\"\n >\n <Minus className=\"size-4\" />\n </Button>\n <input\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => setQuantity(Math.max(1, parseInt(e.target.value) || 1))}\n className=\"flex-1 h-9 rounded-input border border-border-default bg-bg-surface px-3 py-1 text-center text-sm text-text-primary\"\n aria-label={\n bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')\n }\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(quantity + 1)}\n disabled={creating}\n aria-label={t('ggnomad.booking.increaseQuantity', 'Increase quantity')}\n className=\"flex-shrink-0\"\n >\n <Plus className=\"size-4\" />\n </Button>\n </div>\n </div>\n </div>\n\n {/* Pricing breakdown — a per-night total is only meaningful once the stay\n length is known, so it is withheld until both dates are chosen. */}\n {bookingType === 'PROPERTY' && nights < 1 ? (\n <div className=\"mb-6 rounded-md bg-bg-sunken p-3 text-sm text-text-secondary\">\n {t(\n 'ggnomad.booking.selectDatesForTotal',\n 'Select check-in and check-out dates to see the total.'\n )}\n </div>\n ) : (\n <div className=\"space-y-2 mb-6 p-3 rounded-md bg-bg-sunken\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {currency} {unitPrice.toFixed(2)} ×{' '}\n {bookingType === 'PROPERTY'\n ? nights === 1\n ? `1 ${t('ggnomad.booking.night', 'night')}`\n : `${nights} ${t('ggnomad.booking.nights', 'nights')}`\n : `${quantity} ${t('ggnomad.booking.item', 'item')}`}\n </span>\n <span className=\"text-text-primary font-medium\">\n {currency} {subtotal.toFixed(2)}\n </span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">{t('ggnomad.booking.tax', 'Tax (10%)')}</span>\n <span className=\"text-text-primary\">\n {currency} {taxAmount.toFixed(2)}\n </span>\n </div>\n <div className=\"border-t border-border-seam pt-2 flex justify-between\">\n <span className=\"font-semibold text-text-primary\">\n {t('ggnomad.booking.total', 'Total')}\n </span>\n <span className=\"font-bold text-lg text-text-primary\">\n {currency} {total.toFixed(2)}\n </span>\n </div>\n </div>\n )}\n\n {/* Book button */}\n <Button\n onClick={() => void handleBooking()}\n disabled={creating}\n className=\"w-full bg-gradient-to-r from-action-primary-bg to-action-success-bg text-white font-semibold\"\n size=\"lg\"\n >\n {creating\n ? t('ggnomad.booking.button.creating', 'Creating booking…')\n : bookingType === 'PROPERTY'\n ? t('ggnomad.booking.button.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.button.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.button.tour', 'Book this tour')\n : t('ggnomad.booking.button.activity', 'Book this activity')}\n </Button>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ interface EcoAccessPanelProps {
2
+ /** `sustainabilityScore: Int!` — deterministic 0-100 derived from the practices. */
3
+ sustainabilityScore: number;
4
+ /** `sustainabilityPractices: [GgnomadSustainabilityPractice!]!` */
5
+ sustainabilityPractices: string[];
6
+ /** `accessibilityFeatures: [GgnomadAccessibilityFeature!]!` */
7
+ accessibilityFeatures: string[];
8
+ }
9
+ /**
10
+ * BOFF-4364 / BOFF-4379 — the eco + accessibility attestations every listing type
11
+ * carries. Both sets are **self-reported by the provider** and not independently
12
+ * verified, which the panel states outright rather than presenting them as certified.
13
+ * Renders nothing when the listing declares neither.
14
+ */
15
+ export declare function EcoAccessPanel({ sustainabilityScore, sustainabilityPractices, accessibilityFeatures, }: EcoAccessPanelProps): import("react").JSX.Element | null;
16
+ export {};
17
+ //# sourceMappingURL=EcoAccessPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EcoAccessPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/EcoAccessPanel.tsx"],"names":[],"mappings":"AAOA,UAAU,mBAAmB;IAC3B,oFAAoF;IACpF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,GACtB,EAAE,mBAAmB,sCA6DrB"}