@burdenoff/microfe-ggnomad 2026.825.1 → 2026.828.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ggnomad/components/AvailabilityPanel.d.ts +15 -0
- package/dist/ggnomad/components/AvailabilityPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/AvailabilityPanel.js +91 -0
- package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -0
- package/dist/ggnomad/components/BookingPanel.d.ts +1 -3
- package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
- package/dist/ggnomad/components/BookingPanel.js +91 -82
- package/dist/ggnomad/components/BookingPanel.js.map +1 -1
- package/dist/ggnomad/components/EcoAccessPanel.d.ts +17 -0
- package/dist/ggnomad/components/EcoAccessPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/EcoAccessPanel.js +68 -0
- package/dist/ggnomad/components/EcoAccessPanel.js.map +1 -0
- package/dist/ggnomad/components/FactGrid.d.ts +18 -0
- package/dist/ggnomad/components/FactGrid.d.ts.map +1 -0
- package/dist/ggnomad/components/FactGrid.js +29 -0
- package/dist/ggnomad/components/FactGrid.js.map +1 -0
- package/dist/ggnomad/components/GroupTripPanel.d.ts +22 -0
- package/dist/ggnomad/components/GroupTripPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/GroupTripPanel.js +350 -0
- package/dist/ggnomad/components/GroupTripPanel.js.map +1 -0
- package/dist/ggnomad/components/ListingFilters.d.ts +29 -0
- package/dist/ggnomad/components/ListingFilters.d.ts.map +1 -0
- package/dist/ggnomad/components/ListingFilters.js +164 -0
- package/dist/ggnomad/components/ListingFilters.js.map +1 -0
- package/dist/ggnomad/components/PhotoGallery.d.ts +15 -0
- package/dist/ggnomad/components/PhotoGallery.d.ts.map +1 -0
- package/dist/ggnomad/components/PhotoGallery.js +54 -0
- package/dist/ggnomad/components/PhotoGallery.js.map +1 -0
- package/dist/ggnomad/components/ProviderCard.d.ts +19 -0
- package/dist/ggnomad/components/ProviderCard.d.ts.map +1 -0
- package/dist/ggnomad/components/ProviderCard.js +66 -0
- package/dist/ggnomad/components/ProviderCard.js.map +1 -0
- package/dist/ggnomad/components/RatingSummary.d.ts +14 -0
- package/dist/ggnomad/components/RatingSummary.d.ts.map +1 -0
- package/dist/ggnomad/components/RatingSummary.js +32 -0
- package/dist/ggnomad/components/RatingSummary.js.map +1 -0
- package/dist/ggnomad/components/TagChips.d.ts +16 -0
- package/dist/ggnomad/components/TagChips.d.ts.map +1 -0
- package/dist/ggnomad/components/TagChips.js +20 -0
- package/dist/ggnomad/components/TagChips.js.map +1 -0
- package/dist/ggnomad/components/index.d.ts +13 -0
- package/dist/ggnomad/components/index.d.ts.map +1 -1
- package/dist/ggnomad/components/index.js +9 -0
- package/dist/ggnomad/graphql.d.ts +10 -0
- package/dist/ggnomad/graphql.d.ts.map +1 -1
- package/dist/ggnomad/graphql.js +308 -20
- package/dist/ggnomad/graphql.js.map +1 -1
- package/dist/ggnomad/pages/ActivityDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ActivityDetailsPage.js +174 -86
- package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.js +109 -77
- package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +188 -137
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.js +245 -128
- package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.js +115 -53
- package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.js +170 -87
- package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.js +201 -84
- package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.js +99 -56
- package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.js +177 -93
- package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.js +112 -63
- package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
- package/dist/ggnomad/pages/SearchPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/SearchPage.js +236 -65
- package/dist/ggnomad/pages/SearchPage.js.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.js +171 -86
- package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/TourismPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TourismPage.js +108 -54
- package/dist/ggnomad/pages/TourismPage.js.map +1 -1
- package/dist/ggnomad/pages/TravelPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TravelPage.js +406 -43
- package/dist/ggnomad/pages/TravelPage.js.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.js +93 -70
- package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
- package/dist/ggnomad/utils/format.d.ts +47 -0
- package/dist/ggnomad/utils/format.d.ts.map +1 -0
- package/dist/ggnomad/utils/format.js +125 -0
- package/dist/ggnomad/utils/format.js.map +1 -0
- package/dist/stories/BookingPanel.stories.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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,
|
|
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":"
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
|
15
|
-
let { t: w } =
|
|
16
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
34
|
-
itemName:
|
|
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(
|
|
39
|
-
currency:
|
|
43
|
+
totalPrice: Math.round(B * 100) / 100,
|
|
44
|
+
currency: S,
|
|
40
45
|
paymentMethod: "card"
|
|
41
|
-
}, t = await F({ variables: { input:
|
|
46
|
+
}, t = await F({ variables: { input: v === "PROPERTY" ? {
|
|
42
47
|
...e,
|
|
43
48
|
checkIn: k,
|
|
44
49
|
checkOut: j
|
|
45
|
-
} :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
+
a.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
|
|
60
65
|
}
|
|
61
|
-
},
|
|
62
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
72
|
+
/* @__PURE__ */ g("h2", {
|
|
68
73
|
className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
|
|
69
|
-
children:
|
|
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__ */
|
|
76
|
+
/* @__PURE__ */ _("div", {
|
|
72
77
|
className: "space-y-4 mb-6",
|
|
73
|
-
children: [
|
|
78
|
+
children: [v === "PROPERTY" ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
74
79
|
htmlFor: "check-in",
|
|
75
|
-
className:
|
|
80
|
+
className: H,
|
|
76
81
|
children: w("ggnomad.booking.checkIn", "Check-in")
|
|
77
|
-
}), /* @__PURE__ */
|
|
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:
|
|
87
|
+
className: n(U, "mt-2"),
|
|
83
88
|
"aria-label": w("ggnomad.booking.checkIn", "Check-in")
|
|
84
|
-
})] }), /* @__PURE__ */
|
|
89
|
+
})] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
85
90
|
htmlFor: "check-out",
|
|
86
|
-
className:
|
|
91
|
+
className: H,
|
|
87
92
|
children: w("ggnomad.booking.checkOut", "Check-out")
|
|
88
|
-
}), /* @__PURE__ */
|
|
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:
|
|
99
|
+
className: n(U, "mt-2"),
|
|
94
100
|
"aria-label": w("ggnomad.booking.checkOut", "Check-out")
|
|
95
|
-
})] })] }) :
|
|
101
|
+
})] })] }) : v === "EVENT" ? /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
96
102
|
htmlFor: "event-date",
|
|
97
|
-
className:
|
|
103
|
+
className: H,
|
|
98
104
|
children: w("ggnomad.booking.date", "Date")
|
|
99
|
-
}), /* @__PURE__ */
|
|
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:
|
|
110
|
+
className: n(U, "mt-2"),
|
|
105
111
|
"aria-label": w("ggnomad.booking.date", "Date")
|
|
106
|
-
})] }) : /* @__PURE__ */
|
|
112
|
+
})] }) : /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
107
113
|
htmlFor: "booking-date",
|
|
108
|
-
className:
|
|
114
|
+
className: H,
|
|
109
115
|
children: w("ggnomad.booking.date", "Date")
|
|
110
|
-
}), /* @__PURE__ */
|
|
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:
|
|
121
|
+
className: n(U, "mt-2"),
|
|
116
122
|
"aria-label": w("ggnomad.booking.date", "Date")
|
|
117
|
-
})] }), /* @__PURE__ */
|
|
118
|
-
className:
|
|
119
|
-
children:
|
|
120
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
136
|
+
children: /* @__PURE__ */ g(s, { className: "size-4" })
|
|
131
137
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
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":
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
164
|
+
/* @__PURE__ */ _("div", {
|
|
156
165
|
className: "flex justify-between text-sm",
|
|
157
|
-
children: [/* @__PURE__ */
|
|
166
|
+
children: [/* @__PURE__ */ _("span", {
|
|
158
167
|
className: "text-text-secondary",
|
|
159
168
|
children: [
|
|
160
|
-
|
|
169
|
+
S,
|
|
161
170
|
" ",
|
|
162
|
-
|
|
171
|
+
x.toFixed(2),
|
|
163
172
|
" ×",
|
|
164
173
|
" ",
|
|
165
|
-
|
|
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__ */
|
|
176
|
+
}), /* @__PURE__ */ _("span", {
|
|
168
177
|
className: "text-text-primary font-medium",
|
|
169
178
|
children: [
|
|
170
|
-
|
|
179
|
+
S,
|
|
171
180
|
" ",
|
|
172
|
-
|
|
181
|
+
R.toFixed(2)
|
|
173
182
|
]
|
|
174
183
|
})]
|
|
175
184
|
}),
|
|
176
|
-
/* @__PURE__ */
|
|
185
|
+
/* @__PURE__ */ _("div", {
|
|
177
186
|
className: "flex justify-between text-sm",
|
|
178
|
-
children: [/* @__PURE__ */
|
|
187
|
+
children: [/* @__PURE__ */ g("span", {
|
|
179
188
|
className: "text-text-secondary",
|
|
180
189
|
children: w("ggnomad.booking.tax", "Tax (10%)")
|
|
181
|
-
}), /* @__PURE__ */
|
|
190
|
+
}), /* @__PURE__ */ _("span", {
|
|
182
191
|
className: "text-text-primary",
|
|
183
192
|
children: [
|
|
184
|
-
|
|
193
|
+
S,
|
|
185
194
|
" ",
|
|
186
|
-
|
|
195
|
+
z.toFixed(2)
|
|
187
196
|
]
|
|
188
197
|
})]
|
|
189
198
|
}),
|
|
190
|
-
/* @__PURE__ */
|
|
199
|
+
/* @__PURE__ */ _("div", {
|
|
191
200
|
className: "border-t border-border-seam pt-2 flex justify-between",
|
|
192
|
-
children: [/* @__PURE__ */
|
|
201
|
+
children: [/* @__PURE__ */ g("span", {
|
|
193
202
|
className: "font-semibold text-text-primary",
|
|
194
203
|
children: w("ggnomad.booking.total", "Total")
|
|
195
|
-
}), /* @__PURE__ */
|
|
204
|
+
}), /* @__PURE__ */ _("span", {
|
|
196
205
|
className: "font-bold text-lg text-text-primary",
|
|
197
206
|
children: [
|
|
198
|
-
|
|
207
|
+
S,
|
|
199
208
|
" ",
|
|
200
|
-
|
|
209
|
+
B.toFixed(2)
|
|
201
210
|
]
|
|
202
211
|
})]
|
|
203
212
|
})
|
|
204
213
|
]
|
|
205
214
|
}),
|
|
206
|
-
/* @__PURE__ */
|
|
207
|
-
onClick: () => void
|
|
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…") :
|
|
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 {
|
|
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":";;;;;;;;;;;;;AA6BA,SAAgB,EAAa,EAC3B,gBACA,WACA,aACA,cACA,aACA,WACA,gBACoB;CACpB,IAAM,EAAE,SAAM,EAAQ,GAChB,IAAW,EAAY,GACvB,EAAE,cAAW,QAAQ,EAAW,GAIhC,CAAC,GAAc,KAAmB,EAAiB,EAAE,GACrD,CAAC,GAAa,KAAkB,EAAiB,EAAE,GACnD,CAAC,GAAc,KAAmB,EAAiB,EAAE,GAGrD,CAAC,GAAU,KAAe,EAAS,CAAC,GAiBpC,CAAC,GAAe,EAAE,SAAS,OAAc,EAC7C,CAIF,GAIM,IAAW,KADE,MAAgB,cAAc,IAAS,IAAS,IAE7D,IAAY,IAAW,IACvB,IAAQ,IAAW,GAEnB,IAAgB,YAAY;EAEhC,IAAI,MAAgB;OACd,CAAC,KAAe,CAAC,GAAc;IACjC,EAAO,QACL,EAAE,2CAA2C,eAAe,GAC5D,EACE,oDACA,kDACF,CACF;IACA;GACF;SACK,IAAI,MAAgB;OACrB,CAAC,GAAW;IACd,EAAO,QACL,EAAE,0CAA0C,cAAc,GAC1D,EAAE,gDAAgD,qCAAqC,CACzF;IACA;GACF;SAEA,IAAI,CAAC,GAAc;GACjB,EAAO,QACL,EAAE,0CAA0C,cAAc,GAC1D,EAAE,2CAA2C,wCAAwC,CACvF;GACA;EACF;EAGF,IAAI;GACF,IAAM,IAAY;IAChB;IACA;IACA;IACA,WAAW,EAAE,oCAAoC,YAAY;IAC7D,YAAY;IACZ,QAAQ;IACR,YAAY,KAAK,MAAM,IAAQ,GAAG,IAAI;IACtC;IACA,eAAe;GACjB,GASM,IAAS,MAAM,EAAc,EACjC,WAAW,EAAE,OAPb,MAAgB,aACZ;IAAE,GAAG;IAAW,SAAS;IAAa,UAAU;GAAa,IAC7D,MAAgB,UACd;IAAE,GAAG;IAAsB;GAAU,IACrC;IAAE,GAAG;IAAW,WAAW;GAAa,EAG3B,EACrB,CAAC;GAED,IAAI,EAAO,OAAO;IAChB,EAAO,MACL,EAAE,0CAA0C,0BAA0B,GACtE,EAAO,MAAM,OACf;IACA;GACF;GAEA,IAAM,IAAU,EAAO,MAAM;GAW7B,AAVA,EAAO,QACL,EAAE,4CAA4C,mBAAmB,GACjE,IACI,EAAE,2CAA2C,oBAAoB,EAC9D,QAAQ,WAAW,EAAQ,aAAa,EACxC,QAAQ,YAAY,EAAQ,QAAQ,IACvC,CACN,GAGA,EAAS,EAAS,GAAU,UAAU,CAAC;EACzC,SAAS,GAAK;GACZ,EAAO,MACL,EAAE,0CAA0C,0BAA0B,GACtE,aAAe,QAAQ,EAAI,UAAU,OAAO,CAAG,CACjD;EACF;CACF,GAEM,IAAa,yCACb,IAAa;CAEnB,OACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,MAAA;EAAK,WAAU;YAA5C;GACE,kBAAC,MAAD;IAAI,WAAU;cACX,MAAgB,aACb,EAAE,kCAAkC,gBAAgB,IACpD,MAAgB,UACd,EAAE,+BAA+B,cAAc,IAC/C,MAAgB,SACd,EAAE,8BAA8B,gBAAgB,IAChD,EAAE,kCAAkC,oBAAoB;GAC9D,CAAA;GAEJ,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEG,MAAgB,aACf,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAW,WAAW;eAClC,EAAE,2BAA2B,UAAU;IACnC,CAAA,GACP,kBAAC,GAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;KAC9C,WAAW,EAAG,GAAY,MAAM;KAChC,cAAY,EAAE,2BAA2B,UAAU;IACpD,CAAA,CACE,EAAA,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAY,WAAW;eACnC,EAAE,4BAA4B,WAAW;IACrC,CAAA,GACP,kBAAC,GAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAgB,EAAE,OAAO,KAAK;KAC/C,WAAW,EAAG,GAAY,MAAM;KAChC,cAAY,EAAE,4BAA4B,WAAW;IACtD,CAAA,CACE,EAAA,CAAA,CACL,EAAA,CAAA,IACA,MAAgB,UAClB,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAa,WAAW;eACpC,EAAE,wBAAwB,MAAM;IAC5B,CAAA,GACP,kBAAC,GAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO,IAAY,EAAU,MAAM,GAAG,EAAE,KAAK;KAC7C,UAAA;KACA,WAAW,EAAG,GAAY,MAAM;KAChC,cAAY,EAAE,wBAAwB,MAAM;IAC7C,CAAA,CACE,EAAA,CAAA,IAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAe,WAAW;eACtC,EAAE,wBAAwB,MAAM;IAC5B,CAAA,GACP,kBAAC,GAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAgB,EAAE,OAAO,KAAK;KAC/C,WAAW,EAAG,GAAY,MAAM;KAChC,cAAY,EAAE,wBAAwB,MAAM;IAC7C,CAAA,CACE,EAAA,CAAA,GAIP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,WAAW;eACf,MAAgB,aACb,EAAE,0BAA0B,QAAQ,IACpC,MAAgB,UACd,EAAE,2BAA2B,SAAS,IACtC,EAAE,gCAAgC,cAAc;IACjD,CAAA,GACP,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,eAAe,EAAY,KAAK,IAAI,GAAG,IAAW,CAAC,CAAC;OACpD,UAAU,KAAY,KAAK;OAC3B,cAAY,EAAE,oCAAoC,mBAAmB;OACrE,WAAU;iBAEV,kBAAC,GAAD,EAAO,WAAU,SAAU,CAAA;MACrB,CAAA;MACR,kBAAC,SAAD;OACE,MAAK;OACL,KAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAY,KAAK,IAAI,GAAG,SAAS,EAAE,OAAO,KAAK,KAAK,CAAC,CAAC;OACvE,WAAU;OACV,cACE,MAAgB,aACZ,EAAE,0BAA0B,QAAQ,IACpC,MAAgB,UACd,EAAE,2BAA2B,SAAS,IACtC,EAAE,gCAAgC,cAAc;MAEzD,CAAA;MACD,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,eAAe,EAAY,IAAW,CAAC;OACvC,UAAU;OACV,cAAY,EAAE,oCAAoC,mBAAmB;OACrE,WAAU;iBAEV,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA;MACpB,CAAA;KACL;MACF,EAAA,CAAA,CACF;;GAGL,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG;QAAS;QAAE,EAAU,QAAQ,CAAC;QAAE;QAAG;QACnC,MAAgB,aACb,GAAG,KAAU,EAAE,GAAG,EAAE,0BAA0B,QAAQ,MACtD,GAAG,EAAS,GAAG,EAAE,wBAAwB,MAAM;OAC/C;UACN,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG;QAAS;QAAE,EAAS,QAAQ,CAAC;OAC1B;QACH;;KACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAuB,EAAE,uBAAuB,WAAW;MAAQ,CAAA,GACnF,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG;QAAS;QAAE,EAAU,QAAQ,CAAC;OAC3B;QACH;;KACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,yBAAyB,OAAO;MAC/B,CAAA,GACN,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG;QAAS;QAAE,EAAM,QAAQ,CAAC;OACvB;QACH;;IACF;;GAGL,kBAAC,GAAD;IACE,eAAe,KAAK,EAAc;IAClC,UAAU;IACV,WAAU;IACV,MAAK;cAEJ,IACG,EAAE,mCAAmC,mBAAmB,IACxD,MAAgB,aACd,EAAE,mCAAmC,gBAAgB,IACrD,MAAgB,UACd,EAAE,gCAAgC,cAAc,IAChD,MAAgB,SACd,EAAE,+BAA+B,gBAAgB,IACjD,EAAE,mCAAmC,oBAAoB;GAC7D,CAAA;EACC;;AAEf"}
|
|
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"}
|