@voyantjs/bookings-ui 0.35.0 → 0.37.0

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 (55) hide show
  1. package/dist/components/booking-combobox.d.ts +13 -0
  2. package/dist/components/booking-combobox.d.ts.map +1 -0
  3. package/dist/components/booking-combobox.js +44 -0
  4. package/dist/components/booking-create-dialog.d.ts +9 -0
  5. package/dist/components/booking-create-dialog.d.ts.map +1 -1
  6. package/dist/components/booking-create-dialog.js +110 -90
  7. package/dist/components/booking-create-page.d.ts +11 -0
  8. package/dist/components/booking-create-page.d.ts.map +1 -0
  9. package/dist/components/booking-create-page.js +11 -0
  10. package/dist/components/booking-detail-page.d.ts.map +1 -1
  11. package/dist/components/booking-detail-page.js +4 -1
  12. package/dist/components/booking-item-list.d.ts.map +1 -1
  13. package/dist/components/booking-item-list.js +5 -3
  14. package/dist/components/booking-list-filters.d.ts +35 -0
  15. package/dist/components/booking-list-filters.d.ts.map +1 -0
  16. package/dist/components/booking-list-filters.js +148 -0
  17. package/dist/components/booking-list.d.ts.map +1 -1
  18. package/dist/components/booking-list.js +30 -46
  19. package/dist/components/person-picker-section.d.ts +15 -7
  20. package/dist/components/person-picker-section.d.ts.map +1 -1
  21. package/dist/components/person-picker-section.js +100 -21
  22. package/dist/components/price-breakdown-section.d.ts +16 -1
  23. package/dist/components/price-breakdown-section.d.ts.map +1 -1
  24. package/dist/components/price-breakdown-section.js +36 -5
  25. package/dist/components/product-picker-section.d.ts.map +1 -1
  26. package/dist/components/product-picker-section.js +38 -4
  27. package/dist/components/shared-room-section.d.ts +9 -8
  28. package/dist/components/shared-room-section.d.ts.map +1 -1
  29. package/dist/components/shared-room-section.js +67 -14
  30. package/dist/components/traveler-list.d.ts.map +1 -1
  31. package/dist/components/traveler-list.js +27 -16
  32. package/dist/i18n/en.d.ts +284 -1
  33. package/dist/i18n/en.d.ts.map +1 -1
  34. package/dist/i18n/en.js +300 -17
  35. package/dist/i18n/messages.d.ts +255 -1
  36. package/dist/i18n/messages.d.ts.map +1 -1
  37. package/dist/i18n/provider.d.ts +568 -2
  38. package/dist/i18n/provider.d.ts.map +1 -1
  39. package/dist/i18n/ro.d.ts +284 -1
  40. package/dist/i18n/ro.d.ts.map +1 -1
  41. package/dist/i18n/ro.js +301 -18
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +3 -1
  45. package/dist/journey/components/booking-journey.d.ts.map +1 -1
  46. package/dist/journey/components/booking-journey.js +22 -13
  47. package/dist/journey/components/contract-preview-dialog.d.ts.map +1 -1
  48. package/dist/journey/components/contract-preview-dialog.js +9 -4
  49. package/dist/journey/components/journey-steps.d.ts.map +1 -1
  50. package/dist/journey/components/journey-steps.js +94 -72
  51. package/dist/journey/components/side-panel.d.ts.map +1 -1
  52. package/dist/journey/components/side-panel.js +58 -35
  53. package/dist/journey/components/step-header.d.ts.map +1 -1
  54. package/dist/journey/components/step-header.js +3 -19
  55. package/package.json +24 -20
@@ -5,7 +5,9 @@ import { Checkbox } from "@voyantjs/ui/components/checkbox";
5
5
  import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@voyantjs/ui/components/dialog";
6
6
  import { Skeleton } from "@voyantjs/ui/components/skeleton";
7
7
  import * as React from "react";
8
+ import { formatMessage, useBookingsUiMessagesOrDefault } from "../../i18n/index.js";
8
9
  export function ContractPreviewDialog({ open, onOpenChange, variables, previewUrl, acceptLanguage, onAccept, marketingLabel, termsLabel, }) {
10
+ const messages = useBookingsUiMessagesOrDefault();
9
11
  const [loadState, setLoadState] = React.useState("idle");
10
12
  const [errorMessage, setErrorMessage] = React.useState(null);
11
13
  const [renderedHtml, setRenderedHtml] = React.useState("");
@@ -40,7 +42,9 @@ export function ContractPreviewDialog({ open, onOpenChange, variables, previewUr
40
42
  })
41
43
  .then(async (res) => {
42
44
  if (!res.ok) {
43
- throw new Error(`Preview request failed: ${res.status}`);
45
+ throw new Error(formatMessage(messages.bookingJourney.contract.previewRequestFailed, {
46
+ status: res.status,
47
+ }));
44
48
  }
45
49
  const json = (await res.json());
46
50
  if (cancelled)
@@ -48,7 +52,7 @@ export function ContractPreviewDialog({ open, onOpenChange, variables, previewUr
48
52
  const body = json.data?.rendered ?? "";
49
53
  const tmpl = json.data?.template;
50
54
  if (!body || !tmpl) {
51
- throw new Error("Preview response missing rendered body or template metadata");
55
+ throw new Error(messages.bookingJourney.contract.previewMissing);
52
56
  }
53
57
  setRenderedHtml(body);
54
58
  setTemplate({ id: tmpl.id, slug: tmpl.slug, name: tmpl.name });
@@ -78,8 +82,9 @@ export function ContractPreviewDialog({ open, onOpenChange, variables, previewUr
78
82
  renderedHtml,
79
83
  });
80
84
  };
81
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex h-[85vh] w-[95vw] max-w-4xl flex-col gap-0 p-0", children: [_jsxs(DialogHeader, { className: "border-b p-6 pb-4", children: [_jsx(DialogTitle, { children: template?.name ?? "Booking contract" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Please review the contract below. You can scroll through the document, then tick the boxes and click Accept to continue." })] }), _jsxs("div", { className: "flex-1 overflow-hidden bg-muted/30", children: [loadState === "loading" ? (_jsxs("div", { className: "space-y-3 p-6", children: [_jsx(Skeleton, { className: "h-6 w-1/2" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-5/6" }), _jsx(Skeleton, { className: "h-4 w-4/5" }), _jsx(Skeleton, { className: "h-4 w-full" })] })) : null, loadState === "error" ? (_jsx("div", { className: "p-6", children: _jsxs("p", { className: "text-destructive text-sm", children: ["Couldn't load the contract preview: ", errorMessage] }) })) : null, loadState === "ready" ? (_jsx("iframe", { title: `${template?.name ?? "Contract"} preview`, srcDoc: wrapPreviewHtml(renderedHtml), sandbox: "", className: "h-full w-full border-0 bg-white" })) : null] }), _jsxs(DialogFooter, { className: "border-t p-6 sm:flex-col sm:items-stretch sm:gap-3", children: [_jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("label", { className: "flex items-start gap-2", children: [_jsx(Checkbox, { checked: acceptedTerms, onCheckedChange: (v) => setAcceptedTerms(v === true), className: "mt-0.5" }), _jsx("span", { children: termsLabel ?? (_jsx(_Fragment, { children: "I have read and agree to the terms of this contract. I understand that this booking is binding once accepted." })) })] }), _jsxs("label", { className: "flex items-start gap-2", children: [_jsx(Checkbox, { checked: acceptedMarketing, onCheckedChange: (v) => setAcceptedMarketing(v === true), className: "mt-0.5" }), _jsx("span", { children: marketingLabel ??
82
- "I'd like to receive occasional travel offers and updates by email. (You can unsubscribe at any time.)" })] })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { type: "button", disabled: !canAccept, onClick: handleAccept, children: "Accept and continue" })] })] })] }) }));
85
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex h-[85vh] w-[95vw] max-w-4xl flex-col gap-0 p-0", children: [_jsxs(DialogHeader, { className: "border-b p-6 pb-4", children: [_jsx(DialogTitle, { children: template?.name ?? messages.bookingJourney.contract.defaultTitle }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.bookingJourney.contract.description })] }), _jsxs("div", { className: "flex-1 overflow-hidden bg-muted/30", children: [loadState === "loading" ? (_jsxs("div", { className: "space-y-3 p-6", children: [_jsx(Skeleton, { className: "h-6 w-1/2" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-5/6" }), _jsx(Skeleton, { className: "h-4 w-4/5" }), _jsx(Skeleton, { className: "h-4 w-full" })] })) : null, loadState === "error" ? (_jsx("div", { className: "p-6", children: _jsxs("p", { className: "text-destructive text-sm", children: [messages.bookingJourney.contract.errorPrefix, " ", errorMessage] }) })) : null, loadState === "ready" ? (_jsx("iframe", { title: formatMessage(messages.bookingJourney.contract.iframeTitle, {
86
+ name: template?.name ?? messages.bookingJourney.contract.defaultTitle,
87
+ }), srcDoc: wrapPreviewHtml(renderedHtml), sandbox: "", className: "h-full w-full border-0 bg-white" })) : null] }), _jsxs(DialogFooter, { className: "border-t p-6 sm:flex-col sm:items-stretch sm:gap-3", children: [_jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("label", { className: "flex items-start gap-2", children: [_jsx(Checkbox, { checked: acceptedTerms, onCheckedChange: (v) => setAcceptedTerms(v === true), className: "mt-0.5" }), _jsx("span", { children: termsLabel ?? _jsx(_Fragment, { children: messages.bookingJourney.contract.termsLabel }) })] }), _jsxs("label", { className: "flex items-start gap-2", children: [_jsx(Checkbox, { checked: acceptedMarketing, onCheckedChange: (v) => setAcceptedMarketing(v === true), className: "mt-0.5" }), _jsx("span", { children: marketingLabel ?? messages.bookingJourney.contract.marketingLabel })] })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: messages.bookingJourney.contract.cancel }), _jsx(Button, { type: "button", disabled: !canAccept, onClick: handleAccept, children: messages.bookingJourney.contract.acceptAndContinue })] })] })] }) }));
83
88
  }
84
89
  /**
85
90
  * Wrap the rendered template body in a self-contained light-theme HTML
@@ -1 +1 @@
1
- {"version":3,"file":"journey-steps.d.ts","sourceRoot":"","sources":["../../../src/journey/components/journey-steps.tsx"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AAYzE,OAAO,EACL,KAAK,KAAK,EASX,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EACV,sBAAsB,EACtB,2BAA2B,EAC3B,8BAA8B,EAC9B,0BAA0B,EAC3B,MAAM,aAAa,CAAA;AAEpB,UAAU,eAAe;IACvB,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAA;IAC/B,KAAK,EAAE,iBAAiB,CAAA;CACzB;AAMD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,KAAK,GACN,EAAE,eAAe,GAAG;IACnB,YAAY,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;CACrC,GAAG,KAAK,CAAC,YAAY,CAYrB;AA+UD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,uBAAuB,EACvB,YAAY,GACb,EAAE,eAAe,GAAG;IACnB,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,KAAK,CAAC,SAAS,CAAA;IAC5E,YAAY,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;CACrC,GAAG,KAAK,CAAC,YAAY,CA8JrB;AAMD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,2BAA2B,GAC5B,EAAE,eAAe,GAAG;IACnB,2BAA2B,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,KAAK,CAAC,SAAS,CAAA;CACrF,GAAG,KAAK,CAAC,YAAY,CAiFrB;AAmTD,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAmGjG;AA6DD,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CA8C1F;AAyDD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,YAAY,EACZ,kBAAkB,GACnB,EAAE,eAAe,GAAG;IACnB,YAAY,EAAE,2BAA2B,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,KAAK,CAAC,SAAS,CAAA;CAChF,GAAG,KAAK,CAAC,YAAY,CAuFrB;AA8ED,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,OAAO,GACR,EAAE;IACD,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAA;IAC/B,YAAY,EAAE,OAAO,CAAA;IACrB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;IACpC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC7B,GAAG,KAAK,CAAC,YAAY,CA2DrB"}
1
+ {"version":3,"file":"journey-steps.d.ts","sourceRoot":"","sources":["../../../src/journey/components/journey-steps.tsx"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AAYzE,OAAO,EACL,KAAK,KAAK,EASX,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EACV,sBAAsB,EACtB,2BAA2B,EAC3B,8BAA8B,EAC9B,0BAA0B,EAC3B,MAAM,aAAa,CAAA;AAEpB,UAAU,eAAe;IACvB,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAA;IAC/B,KAAK,EAAE,iBAAiB,CAAA;CACzB;AAMD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,KAAK,GACN,EAAE,eAAe,GAAG;IACnB,YAAY,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;CACrC,GAAG,KAAK,CAAC,YAAY,CAarB;AAiWD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,uBAAuB,EACvB,YAAY,GACb,EAAE,eAAe,GAAG;IACnB,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,KAAK,CAAC,SAAS,CAAA;IAC5E,YAAY,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;CACrC,GAAG,KAAK,CAAC,YAAY,CA+JrB;AAMD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,2BAA2B,GAC5B,EAAE,eAAe,GAAG;IACnB,2BAA2B,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,KAAK,CAAC,SAAS,CAAA;CACrF,GAAG,KAAK,CAAC,YAAY,CAgFrB;AAkUD,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAsGjG;AAkED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAkD1F;AAyDD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,YAAY,EACZ,kBAAkB,GACnB,EAAE,eAAe,GAAG;IACnB,YAAY,EAAE,2BAA2B,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,KAAK,CAAC,SAAS,CAAA;CAChF,GAAG,KAAK,CAAC,YAAY,CAoFrB;AAwDD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,OAAO,GACR,EAAE;IACD,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAA;IAC/B,YAAY,EAAE,OAAO,CAAA;IACrB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAA;IACpC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC7B,GAAG,KAAK,CAAC,YAAY,CAgErB"}
@@ -10,27 +10,36 @@ import { PhoneInput } from "@voyantjs/ui/components/phone-input";
10
10
  import { RadioGroup, RadioGroupItem } from "@voyantjs/ui/components/radio-group";
11
11
  import { Textarea } from "@voyantjs/ui/components/textarea";
12
12
  import { Loader2 } from "lucide-react";
13
+ import { formatMessage, useBookingsUiMessagesOrDefault } from "../../i18n/index.js";
13
14
  import { patchBilling, patchConfigure, patchPaxCount, setAccommodation, setAddons, setPayment, setTravelers, totalPax, } from "../lib/draft-state.js";
14
15
  // ─────────────────────────────────────────────────────────────────
15
16
  // Configure
16
17
  // ─────────────────────────────────────────────────────────────────
17
18
  export function ConfigureStep({ draft, setDraft, shape, }) {
18
- return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Configure" }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx(PaxBands, { draft: draft, setDraft: setDraft, shape: shape }), _jsx(DepartureFields, { draft: draft, setDraft: setDraft, shape: shape })] })] }));
19
+ const messages = useBookingsUiMessagesOrDefault();
20
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.steps.configure }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx(PaxBands, { draft: draft, setDraft: setDraft, shape: shape }), _jsx(DepartureFields, { draft: draft, setDraft: setDraft, shape: shape })] })] }));
19
21
  }
20
22
  function PaxBands({ draft, setDraft, shape }) {
21
- return (_jsxs("div", { className: "space-y-3", children: [_jsx(Label, { children: "Travelers" }), _jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: shape.paxBands.map((band) => {
23
+ const messages = useBookingsUiMessagesOrDefault();
24
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(Label, { children: messages.bookingJourney.configure.travelers }), _jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: shape.paxBands.map((band) => {
22
25
  const value = draft.configure.pax?.[band.code] ?? 0;
23
- return (_jsxs("div", { className: "flex items-center justify-between rounded border p-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: band.label }), band.minAge != null || band.maxAge != null ? (_jsx("div", { className: "text-muted-foreground text-xs", children: ageHint(band.minAge, band.maxAge) })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", type: "button", disabled: value <= band.minCount, onClick: () => setDraft(patchPaxCount(draft, band.code, value - 1)), children: "\u2212" }), _jsx("span", { className: "min-w-6 text-center", children: value }), _jsx(Button, { variant: "outline", size: "sm", type: "button", disabled: value >= band.maxCount, onClick: () => setDraft(patchPaxCount(draft, band.code, value + 1)), children: "+" })] })] }, band.code));
26
+ return (_jsxs("div", { className: "flex items-center justify-between rounded border p-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: band.label }), band.minAge != null || band.maxAge != null ? (_jsx("div", { className: "text-muted-foreground text-xs", children: ageHint(band.minAge, band.maxAge, messages) })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", type: "button", disabled: value <= band.minCount, onClick: () => setDraft(patchPaxCount(draft, band.code, value - 1)), children: "\u2212" }), _jsx("span", { className: "min-w-6 text-center", children: value }), _jsx(Button, { variant: "outline", size: "sm", type: "button", disabled: value >= band.maxCount, onClick: () => setDraft(patchPaxCount(draft, band.code, value + 1)), children: "+" })] })] }, band.code));
24
27
  }) }), _jsx(PaxValidation, { draft: draft, shape: shape })] }));
25
28
  }
26
29
  function PaxValidation({ draft, shape, }) {
30
+ const messages = useBookingsUiMessagesOrDefault();
27
31
  const total = totalPax(draft);
28
32
  const { min, max } = shape.paxBandsAllowedTotal;
29
33
  if (total < min) {
30
- return (_jsxs("p", { className: "text-sm text-amber-600", children: ["Add at least ", min, " traveler", min === 1 ? "" : "s", " to continue."] }));
34
+ return (_jsx("p", { className: "text-sm text-amber-600", children: formatMessage(messages.bookingJourney.validation.addAtLeastTravelers, {
35
+ count: min,
36
+ plural: min === 1 ? "" : "s",
37
+ }) }));
31
38
  }
32
39
  if (total > max) {
33
- return _jsxs("p", { className: "text-sm text-destructive", children: ["Max ", max, " travelers per booking."] });
40
+ return (_jsx("p", { className: "text-sm text-destructive", children: formatMessage(messages.bookingJourney.validation.maxTravelersPerBooking, {
41
+ count: max,
42
+ }) }));
34
43
  }
35
44
  return null;
36
45
  }
@@ -64,52 +73,60 @@ function DepartureFields({ draft, setDraft, shape }) {
64
73
  }) }));
65
74
  }
66
75
  function DepartureBasic({ draft, setDraft, }) {
67
- return (_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(DateField, { id: "bj-departure-date", label: "Departure date", value: draft.configure.departureDate ?? "", onChange: (v) => setDraft(patchConfigure(draft, { departureDate: v })), range: "future" }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "bj-departure-time", children: "Time (optional)" }), _jsx(Input, { id: "bj-departure-time", type: "time", value: draft.configure.departureTime ?? "", onChange: (e) => setDraft(patchConfigure(draft, { departureTime: e.target.value })) })] })] }));
76
+ const messages = useBookingsUiMessagesOrDefault();
77
+ return (_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(DateField, { id: "bj-departure-date", label: messages.bookingJourney.configure.departureDate, value: draft.configure.departureDate ?? "", onChange: (v) => setDraft(patchConfigure(draft, { departureDate: v })), range: "future" }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "bj-departure-time", children: messages.bookingJourney.configure.timeOptional }), _jsx(Input, { id: "bj-departure-time", type: "time", value: draft.configure.departureTime ?? "", onChange: (e) => setDraft(patchConfigure(draft, { departureTime: e.target.value })) })] })] }));
68
78
  }
69
79
  function DateRangeFields({ draft, setDraft, minNights, maxNights, }) {
80
+ const messages = useBookingsUiMessagesOrDefault();
70
81
  const range = draft.configure.dateRange;
71
- return (_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(DateField, { id: "bj-checkin", label: "Check-in", value: range?.checkIn ?? "", onChange: (v) => setDraft(patchConfigure(draft, {
82
+ return (_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(DateField, { id: "bj-checkin", label: messages.bookingJourney.configure.checkIn, value: range?.checkIn ?? "", onChange: (v) => setDraft(patchConfigure(draft, {
72
83
  dateRange: { checkIn: v, checkOut: range?.checkOut ?? "" },
73
- })), range: "future" }), _jsx(DateField, { id: "bj-checkout", label: `Check-out (${minNights}–${maxNights} nights)`, value: range?.checkOut ?? "", onChange: (v) => setDraft(patchConfigure(draft, {
84
+ })), range: "future" }), _jsx(DateField, { id: "bj-checkout", label: formatMessage(messages.bookingJourney.configure.checkOutWithNights, {
85
+ minNights,
86
+ maxNights,
87
+ }), value: range?.checkOut ?? "", onChange: (v) => setDraft(patchConfigure(draft, {
74
88
  dateRange: { checkIn: range?.checkIn ?? "", checkOut: v },
75
89
  })), range: "future" })] }));
76
90
  }
77
91
  function CabinCategoryFields({ draft, setDraft, categories, }) {
78
- return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Cabin category" }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: categories.map((cat) => {
92
+ const messages = useBookingsUiMessagesOrDefault();
93
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: messages.bookingJourney.configure.cabinCategory }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: categories.map((cat) => {
79
94
  const selected = draft.configure.cabinCategoryId === cat.id;
80
95
  return (_jsxs("button", { type: "button", className: `rounded border p-3 text-left ${selected ? "border-primary ring-2 ring-primary" : ""}`, onClick: () => setDraft(patchConfigure(draft, { cabinCategoryId: cat.id, cabinNumberId: undefined })), children: [_jsx("div", { className: "font-medium", children: cat.name }), cat.description ? (_jsx("div", { className: "text-muted-foreground text-xs", children: cat.description })) : null] }, cat.id));
81
96
  }) })] }));
82
97
  }
83
98
  function AirArrangementFields({ draft, setDraft, }) {
99
+ const messages = useBookingsUiMessagesOrDefault();
84
100
  const current = draft.configure.airArrangement;
85
101
  const options = [
86
102
  {
87
103
  value: "cruise_line",
88
- label: "Cruise-line-arranged flights",
89
- description: "The cruise line books your flights in a coordinated package. Operator follows up with the air desk.",
104
+ label: messages.bookingJourney.configure.airOptions.cruise_line.label,
105
+ description: messages.bookingJourney.configure.airOptions.cruise_line.description,
90
106
  },
91
107
  {
92
108
  value: "independent",
93
- label: "Independent flights",
94
- description: "Book flights yourself or via a separate flight booking line.",
109
+ label: messages.bookingJourney.configure.airOptions.independent.label,
110
+ description: messages.bookingJourney.configure.airOptions.independent.description,
95
111
  },
96
112
  {
97
113
  value: "none",
98
- label: "No flights needed",
99
- description: "Regional cruise / driving to the port.",
114
+ label: messages.bookingJourney.configure.airOptions.none.label,
115
+ description: messages.bookingJourney.configure.airOptions.none.description,
100
116
  },
101
117
  ];
102
- return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Air arrangements" }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-3", children: options.map((opt) => {
118
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: messages.bookingJourney.configure.airArrangements }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-3", children: options.map((opt) => {
103
119
  const selected = current === opt.value;
104
120
  return (_jsxs("button", { type: "button", className: `rounded border p-3 text-left text-sm ${selected ? "border-primary ring-2 ring-primary" : ""}`, onClick: () => setDraft(patchConfigure(draft, { airArrangement: opt.value })), children: [_jsx("div", { className: "font-medium", children: opt.label }), _jsx("div", { className: "text-muted-foreground text-xs", children: opt.description })] }, opt.value));
105
121
  }) })] }));
106
122
  }
107
123
  function CabinNumberFields({ draft, setDraft, perCategory, }) {
124
+ const messages = useBookingsUiMessagesOrDefault();
108
125
  const catId = draft.configure.cabinCategoryId;
109
126
  if (!catId)
110
127
  return null;
111
128
  const cabins = perCategory[catId] ?? [];
112
- return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Cabin number" }), _jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4", children: cabins.map((cabin) => {
129
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: messages.bookingJourney.configure.cabinNumber }), _jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4", children: cabins.map((cabin) => {
113
130
  const selected = draft.configure.cabinNumberId === cabin.id;
114
131
  return (_jsx("button", { type: "button", className: `rounded border p-2 text-sm ${selected ? "border-primary ring-2 ring-primary" : ""}`, onClick: () => setDraft(patchConfigure(draft, { cabinNumberId: cabin.id })), children: cabin.label }, cabin.id));
115
132
  }) })] }));
@@ -118,6 +135,7 @@ function CabinNumberFields({ draft, setDraft, perCategory, }) {
118
135
  // Billing
119
136
  // ─────────────────────────────────────────────────────────────────
120
137
  export function BillingStep({ draft, setDraft, renderLeadContactPicker, renderExtras, }) {
138
+ const messages = useBookingsUiMessagesOrDefault();
121
139
  const billing = draft.billing;
122
140
  const apply = (contact) => {
123
141
  setDraft(patchBilling(draft, {
@@ -129,11 +147,11 @@ export function BillingStep({ draft, setDraft, renderLeadContactPicker, renderEx
129
147
  },
130
148
  }));
131
149
  };
132
- return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Billing & lead contact" }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Buyer type" }), _jsxs(RadioGroup, { value: billing.buyerType, onValueChange: (v) => setDraft(patchBilling(draft, { buyerType: v })), className: "flex gap-4", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(RadioGroupItem, { value: "B2C" }), " Individual (B2C)"] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(RadioGroupItem, { value: "B2B" }), " Company (B2B)"] })] })] }), renderLeadContactPicker ? _jsx("div", { children: renderLeadContactPicker({ apply }) }) : null, _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: "bj-billing-firstName", label: "First name", value: billing.contact.firstName, onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, firstName: v } })) }), _jsx(Field, { id: "bj-billing-lastName", label: "Last name", value: billing.contact.lastName, onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, lastName: v } })) }), _jsx(Field, { id: "bj-billing-email", label: "Email", type: "email", value: billing.contact.email, onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, email: v } })) }), _jsx(PhoneField, { id: "bj-billing-phone", label: "Phone", value: billing.contact.phone ?? "", onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, phone: v } })) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: "bj-billing-line1", label: "Address line 1", value: billing.address.line1 ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, line1: v } })) }), _jsx(Field, { id: "bj-billing-line2", label: "Address line 2 (optional)", value: billing.address.line2 ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, line2: v } })) }), _jsx(Field, { id: "bj-billing-city", label: "City", value: billing.address.city ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, city: v } })) }), _jsx(Field, { id: "bj-billing-postal", label: "Postal code", value: billing.address.postal ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, postal: v } })) }), _jsxs("div", { className: "space-y-1 sm:col-span-2", children: [_jsx(Label, { htmlFor: "bj-billing-country", children: "Country" }), _jsx(CountryCombobox, { value: billing.address.country ?? null, onChange: (code) => setDraft(patchBilling(draft, {
150
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.billing.title }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: messages.bookingJourney.billing.buyerType }), _jsxs(RadioGroup, { value: billing.buyerType, onValueChange: (v) => setDraft(patchBilling(draft, { buyerType: v })), className: "flex gap-4", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(RadioGroupItem, { value: "B2C" }), " ", messages.bookingJourney.billing.individual] }), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx(RadioGroupItem, { value: "B2B" }), " ", messages.bookingJourney.billing.company] })] })] }), renderLeadContactPicker ? _jsx("div", { children: renderLeadContactPicker({ apply }) }) : null, _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: "bj-billing-firstName", label: messages.bookingJourney.billing.firstName, value: billing.contact.firstName, onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, firstName: v } })) }), _jsx(Field, { id: "bj-billing-lastName", label: messages.bookingJourney.billing.lastName, value: billing.contact.lastName, onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, lastName: v } })) }), _jsx(Field, { id: "bj-billing-email", label: messages.bookingJourney.billing.email, type: "email", value: billing.contact.email, onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, email: v } })) }), _jsx(PhoneField, { id: "bj-billing-phone", label: messages.bookingJourney.billing.phone, value: billing.contact.phone ?? "", onChange: (v) => setDraft(patchBilling(draft, { contact: { ...billing.contact, phone: v } })) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: "bj-billing-line1", label: messages.bookingJourney.billing.addressLine1, value: billing.address.line1 ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, line1: v } })) }), _jsx(Field, { id: "bj-billing-line2", label: messages.bookingJourney.billing.addressLine2Optional, value: billing.address.line2 ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, line2: v } })) }), _jsx(Field, { id: "bj-billing-city", label: messages.bookingJourney.billing.city, value: billing.address.city ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, city: v } })) }), _jsx(Field, { id: "bj-billing-postal", label: messages.bookingJourney.billing.postalCode, value: billing.address.postal ?? "", onChange: (v) => setDraft(patchBilling(draft, { address: { ...billing.address, postal: v } })) }), _jsxs("div", { className: "space-y-1 sm:col-span-2", children: [_jsx(Label, { htmlFor: "bj-billing-country", children: messages.bookingJourney.billing.country }), _jsx(CountryCombobox, { value: billing.address.country ?? null, onChange: (code) => setDraft(patchBilling(draft, {
133
151
  address: { ...billing.address, country: code ?? "" },
134
- })) })] })] }), billing.buyerType === "B2B" ? (_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: "bj-billing-companyName", label: "Company name", value: billing.company?.name ?? "", onChange: (v) => setDraft(patchBilling(draft, {
152
+ })) })] })] }), billing.buyerType === "B2B" ? (_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: "bj-billing-companyName", label: messages.bookingJourney.billing.companyName, value: billing.company?.name ?? "", onChange: (v) => setDraft(patchBilling(draft, {
135
153
  company: { ...(billing.company ?? { name: "" }), name: v },
136
- })) }), _jsx(Field, { id: "bj-billing-vatId", label: "VAT id", value: billing.company?.vatId ?? "", onChange: (v) => setDraft(patchBilling(draft, {
154
+ })) }), _jsx(Field, { id: "bj-billing-vatId", label: messages.bookingJourney.billing.vatId, value: billing.company?.vatId ?? "", onChange: (v) => setDraft(patchBilling(draft, {
137
155
  company: { ...(billing.company ?? { name: "" }), vatId: v },
138
156
  })) })] })) : null, renderExtras ? _jsx("div", { children: renderExtras() }) : null] })] }));
139
157
  }
@@ -141,6 +159,7 @@ export function BillingStep({ draft, setDraft, renderLeadContactPicker, renderEx
141
159
  // Travelers
142
160
  // ─────────────────────────────────────────────────────────────────
143
161
  export function TravelersStep({ draft, setDraft, shape, renderTravelerContactPicker, }) {
162
+ const messages = useBookingsUiMessagesOrDefault();
144
163
  const total = totalPax(draft);
145
164
  // Auto-resize the travelers list to match pax counts. Newly-added
146
165
  // rows pick a band based on the lowest-count band that's not yet
@@ -174,7 +193,7 @@ export function TravelersStep({ draft, setDraft, shape, renderTravelerContactPic
174
193
  return;
175
194
  setDraft(patchPaxCount(draft, defaultAddBand.code, current + 1));
176
195
  };
177
- return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Travelers" }) }), _jsxs(CardContent, { className: "space-y-4", children: [ensured.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Pick traveler counts in the Configure step to start adding details." })) : null, ensured.map((traveler, idx) => {
196
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.travelers.title }) }), _jsxs(CardContent, { className: "space-y-4", children: [ensured.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: messages.bookingJourney.travelers.empty })) : null, ensured.map((traveler, idx) => {
178
197
  const apply = (contact) => {
179
198
  const next = [...ensured];
180
199
  next[idx] = {
@@ -190,7 +209,7 @@ export function TravelersStep({ draft, setDraft, shape, renderTravelerContactPic
190
209
  const currentBandCount = draft.configure.pax?.[traveler.band] ?? 0;
191
210
  const canRemove = !bandSpec || currentBandCount > bandSpec.minCount;
192
211
  return (_jsx(TravelerCard, { idx: idx, traveler: traveler, shape: shape, draft: draft, setDraft: setDraft, renderTravelerContactPicker: renderTravelerContactPicker, apply: apply, onRemove: canRemove ? () => removeTraveler(idx) : undefined }, traveler.rowId ?? idx));
193
- }), canAddMore ? (_jsx("div", { className: "border-t pt-3", children: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: addTraveler, children: "+ Add traveler" }) })) : null] })] }));
212
+ }), canAddMore ? (_jsx("div", { className: "border-t pt-3", children: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: addTraveler, children: messages.bookingJourney.travelers.addTraveler }) })) : null] })] }));
194
213
  }
195
214
  /**
196
215
  * One traveler block — name, optional contact, age, and any
@@ -204,6 +223,7 @@ export function TravelersStep({ draft, setDraft, shape, renderTravelerContactPic
204
223
  * touching the wizard.
205
224
  */
206
225
  function TravelerCard({ idx, traveler, shape, draft, setDraft, renderTravelerContactPicker, apply, onRemove, }) {
226
+ const messages = useBookingsUiMessagesOrDefault();
207
227
  // The band is bookkeeping — only show fields that apply to this
208
228
  // band per the descriptor, but never expose the band tag in the UI.
209
229
  // The user just sees a flat traveler list.
@@ -264,13 +284,24 @@ function TravelerCard({ idx, traveler, shape, draft, setDraft, renderTravelerCon
264
284
  phone: billingContact.phone || undefined,
265
285
  });
266
286
  };
267
- return (_jsxs("div", { className: "rounded border p-4 space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("div", { className: "space-y-0.5", children: _jsxs("div", { className: "font-medium", children: ["Traveler ", idx + 1, computedAge != null ? (_jsxs("span", { className: "text-muted-foreground font-normal", children: [" \u00B7 age ", computedAge] })) : null] }) }), _jsxs("div", { className: "flex items-center gap-2", children: [canCopyFromBilling ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: copyFromBilling, children: "Copy from billing" })) : null, renderTravelerContactPicker
287
+ return (_jsxs("div", { className: "rounded border p-4 space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("div", { className: "space-y-0.5", children: _jsxs("div", { className: "font-medium", children: [formatMessage(messages.bookingJourney.travelers.travelerNumber, {
288
+ number: idx + 1,
289
+ }), computedAge != null ? (_jsxs("span", { className: "text-muted-foreground font-normal", children: [" ", "\u00B7", " ", formatMessage(messages.bookingJourney.travelers.ageLabel, {
290
+ age: computedAge,
291
+ })] })) : null] }) }), _jsxs("div", { className: "flex items-center gap-2", children: [canCopyFromBilling ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: copyFromBilling, children: messages.bookingJourney.travelers.copyFromBilling })) : null, renderTravelerContactPicker
268
292
  ? renderTravelerContactPicker({ rowIndex: idx, apply })
269
- : null, onRemove ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", onClick: onRemove, children: "Remove" })) : null] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: `bj-trav-${idx}-first`, label: "First name", value: traveler.firstName, onChange: (v) => updateTraveler(draft, setDraft, idx, { firstName: v }) }), _jsx(Field, { id: `bj-trav-${idx}-last`, label: "Last name", value: traveler.lastName, onChange: (v) => updateTraveler(draft, setDraft, idx, { lastName: v }) }), applicableFields.some((f) => f.key === "email") ? (_jsx(Field, { id: `bj-trav-${idx}-email`, label: "Email", type: "email", value: traveler.email ?? "", onChange: (v) => updateTraveler(draft, setDraft, idx, { email: v }) })) : null, phoneField ? (_jsx(PhoneField, { id: `bj-trav-${idx}-phone`, label: phoneField.label + (phoneField.required ? " *" : ""), value: traveler.phone ?? "", onChange: (v) => updateTraveler(draft, setDraft, idx, { phone: v }) })) : null, dobField ? (_jsxs("div", { className: "space-y-1", children: [_jsx(DateField, { id: `bj-trav-${idx}-dob`, label: dobField.label + (dobField.required ? " *" : ""), value: traveler.dateOfBirth ?? "", onChange: onDobChange, range: "past" }), ageOutOfBounds ? (_jsxs("p", { className: "text-amber-600 text-xs dark:text-amber-400", children: ["\u26A0 Age ", computedAge, " is outside the accepted range for this product."] })) : null] })) : null, dynamicFields.map((field) => {
293
+ : null, onRemove ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", onClick: onRemove, children: messages.bookingJourney.travelers.remove })) : null] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [_jsx(Field, { id: `bj-trav-${idx}-first`, label: messages.bookingJourney.billing.firstName, value: traveler.firstName, onChange: (v) => updateTraveler(draft, setDraft, idx, { firstName: v }) }), _jsx(Field, { id: `bj-trav-${idx}-last`, label: messages.bookingJourney.billing.lastName, value: traveler.lastName, onChange: (v) => updateTraveler(draft, setDraft, idx, { lastName: v }) }), applicableFields.some((f) => f.key === "email") ? (_jsx(Field, { id: `bj-trav-${idx}-email`, label: messages.bookingJourney.billing.email, type: "email", value: traveler.email ?? "", onChange: (v) => updateTraveler(draft, setDraft, idx, { email: v }) })) : null, phoneField ? (_jsx(PhoneField, { id: `bj-trav-${idx}-phone`,
294
+ // i18n-literal-ok Required marker appended to a descriptor-supplied field label.
295
+ label: phoneField.label + (phoneField.required ? " *" : ""), value: traveler.phone ?? "", onChange: (v) => updateTraveler(draft, setDraft, idx, { phone: v }) })) : null, dobField ? (_jsxs("div", { className: "space-y-1", children: [_jsx(DateField, { id: `bj-trav-${idx}-dob`,
296
+ // i18n-literal-ok Required marker appended to a descriptor-supplied field label.
297
+ label: dobField.label + (dobField.required ? " *" : ""), value: traveler.dateOfBirth ?? "", onChange: onDobChange, range: "past" }), ageOutOfBounds ? (_jsxs("p", { className: "text-amber-600 text-xs dark:text-amber-400", children: ["\u26A0", " ", formatMessage(messages.bookingJourney.validation.ageOutOfRange, {
298
+ age: computedAge,
299
+ })] })) : null] })) : null, dynamicFields.map((field) => {
270
300
  const value = traveler.documents?.[field.key] ?? "";
271
301
  const onFieldChange = (v) => updateTraveler(draft, setDraft, idx, {
272
302
  documents: { ...traveler.documents, [field.key]: v },
273
303
  });
304
+ // i18n-literal-ok Required marker appended to a descriptor-supplied field label.
274
305
  const labelText = field.label + (field.required ? " *" : "");
275
306
  if (field.type === "select" && field.options) {
276
307
  return (_jsx(SelectField, { id: `bj-trav-${idx}-${field.key}`, label: labelText, value: value, options: field.options, onChange: onFieldChange }, field.key));
@@ -347,10 +378,11 @@ function rebandTraveler(draft, idx, fromBand, toBand) {
347
378
  // Accommodation
348
379
  // ─────────────────────────────────────────────────────────────────
349
380
  export function AccommodationStep({ draft, setDraft, shape }) {
381
+ const messages = useBookingsUiMessagesOrDefault();
350
382
  const subSteps = shape.accommodation?.subSteps ?? [];
351
383
  const rooms = shape.accommodation?.roomOptions ?? [];
352
384
  const accommodation = draft.accommodation ?? { rooms: [], travelerAssignments: {} };
353
- return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Accommodation" }) }), _jsx(CardContent, { className: "space-y-4", children: rooms.length === 0 && subSteps.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "No accommodation options for this product." })) : (_jsxs("div", { className: "space-y-3", children: [rooms.map((room) => {
385
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.accommodation.title }) }), _jsx(CardContent, { className: "space-y-4", children: rooms.length === 0 && subSteps.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: messages.bookingJourney.accommodation.empty })) : (_jsxs("div", { className: "space-y-3", children: [rooms.map((room) => {
354
386
  const current = accommodation.rooms.find((r) => r.optionUnitId === room.id);
355
387
  const ratePlans = room.ratePlans ?? [];
356
388
  return (_jsxs("div", { className: "space-y-3 rounded border p-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: room.name }), room.description ? (_jsx("div", { className: "text-muted-foreground text-xs", children: room.description })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", type: "button", onClick: () => {
@@ -377,26 +409,31 @@ export function AccommodationStep({ draft, setDraft, shape }) {
377
409
  const list = accommodation.rooms.map((r) => r.optionUnitId === room.id ? { ...r, ratePlanId: planId } : r);
378
410
  setDraft(setAccommodation(draft, { ...accommodation, rooms: list }));
379
411
  } })) : null] }, room.id));
380
- }), subSteps.map((sub) => sub.kind === "extensions" ? (_jsxs("div", { className: "rounded border p-3 text-muted-foreground text-sm", children: [sub.options.length, " extension option", sub.options.length === 1 ? "" : "s", " ", "available \u2014 UI lands in Phase F."] }, "extensions")) : null)] })) })] }));
412
+ }), subSteps.map((sub) => sub.kind === "extensions" ? (_jsx("div", { className: "rounded border p-3 text-muted-foreground text-sm", children: formatMessage(messages.bookingJourney.accommodation.extensionsAvailable, {
413
+ count: sub.options.length,
414
+ plural: sub.options.length === 1 ? "" : "s",
415
+ }) }, "extensions")) : null)] })) })] }));
381
416
  }
382
417
  function RatePlanPicker({ roomId, ratePlans, selected, onSelect, }) {
383
- return (_jsxs("div", { className: "space-y-2 border-t pt-3", children: [_jsx(Label, { htmlFor: `bj-rate-plan-${roomId}`, children: "Rate plan" }), _jsx("div", { className: "space-y-2", children: ratePlans.map((plan) => {
418
+ const messages = useBookingsUiMessagesOrDefault();
419
+ return (_jsxs("div", { className: "space-y-2 border-t pt-3", children: [_jsx(Label, { htmlFor: `bj-rate-plan-${roomId}`, children: messages.bookingJourney.accommodation.ratePlan }), _jsx("div", { className: "space-y-2", children: ratePlans.map((plan) => {
384
420
  const isSelected = plan.id === selected;
385
- return (_jsxs("button", { type: "button", onClick: () => onSelect(plan.id), className: `w-full rounded border p-2 text-left text-sm ${isSelected ? "border-primary ring-2 ring-primary" : ""}`, children: [_jsx("div", { className: "font-medium", children: plan.name }), plan.description ? (_jsx("div", { className: "text-muted-foreground text-xs", children: plan.description })) : null, plan.cancellationPolicy ? (_jsxs("div", { className: "text-muted-foreground text-xs", children: ["Cancellation: ", plan.cancellationPolicy] })) : null, plan.inclusions && plan.inclusions.length > 0 ? (_jsxs("div", { className: "text-muted-foreground text-xs", children: ["Includes: ", plan.inclusions.join(", ")] })) : null] }, plan.id));
421
+ return (_jsxs("button", { type: "button", onClick: () => onSelect(plan.id), className: `w-full rounded border p-2 text-left text-sm ${isSelected ? "border-primary ring-2 ring-primary" : ""}`, children: [_jsx("div", { className: "font-medium", children: plan.name }), plan.description ? (_jsx("div", { className: "text-muted-foreground text-xs", children: plan.description })) : null, plan.cancellationPolicy ? (_jsxs("div", { className: "text-muted-foreground text-xs", children: [messages.bookingJourney.accommodation.cancellationPrefix, " ", plan.cancellationPolicy] })) : null, plan.inclusions && plan.inclusions.length > 0 ? (_jsxs("div", { className: "text-muted-foreground text-xs", children: [messages.bookingJourney.accommodation.includesPrefix, " ", plan.inclusions.join(", ")] })) : null] }, plan.id));
386
422
  }) })] }));
387
423
  }
388
424
  // ─────────────────────────────────────────────────────────────────
389
425
  // Add-ons
390
426
  // ─────────────────────────────────────────────────────────────────
391
427
  export function AddonsStep({ draft, setDraft, shape }) {
428
+ const messages = useBookingsUiMessagesOrDefault();
392
429
  const flat = shape.addons?.catalog ?? [];
393
430
  const groups = shape.addons?.groups ?? [];
394
431
  const all = [...flat, ...groups.flatMap((g) => g.items)];
395
- return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Add-ons" }) }), _jsxs(CardContent, { className: "space-y-4", children: [all.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "No add-ons available for this product." })) : null, groups.map((group) => {
432
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.addons.title }) }), _jsxs(CardContent, { className: "space-y-4", children: [all.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: messages.bookingJourney.addons.empty })) : null, groups.map((group) => {
396
433
  // Group by port/day when the descriptor asks — cruise
397
434
  // excursions arrive grouped by port name.
398
435
  const buckets = group.groupBy === "port" || group.groupBy === "day"
399
- ? bucketBy(group.items, (i) => i.groupKey ?? "Other")
436
+ ? bucketBy(group.items, (i) => i.groupKey ?? messages.bookingJourney.addons.otherBucket)
400
437
  : new Map([["", group.items]]);
401
438
  return (_jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "font-medium text-sm", children: group.label }), [...buckets.entries()].map(([bucket, items]) => (_jsxs("div", { className: "space-y-2", children: [bucket ? (_jsx("div", { className: "text-muted-foreground text-xs uppercase", children: bucket })) : null, items.map((item) => (_jsx(AddonRow, { draft: draft, setDraft: setDraft, item: item }, item.id)))] }, bucket || "all")))] }, group.label));
402
439
  }), flat.length > 0 && groups.length === 0 ? (_jsx("div", { className: "space-y-2", children: flat.map((item) => (_jsx(AddonRow, { draft: draft, setDraft: setDraft, item: item }, item.id))) })) : null] })] }));
@@ -420,6 +457,7 @@ function AddonRow({ draft, setDraft, item, }) {
420
457
  // Payment
421
458
  // ─────────────────────────────────────────────────────────────────
422
459
  export function PaymentStep({ draft, setDraft, shape, capabilities, renderProviderStep, }) {
460
+ const messages = useBookingsUiMessagesOrDefault();
423
461
  // The descriptor lists what the *engine* supports; capabilities
424
462
  // narrow further to what the *deployment* turned on. Both must
425
463
  // accept an intent for the user to see it.
@@ -431,8 +469,8 @@ export function PaymentStep({ draft, setDraft, shape, capabilities, renderProvid
431
469
  if (allowed.length > 0 && !allowed.includes(intent)) {
432
470
  setDraft(setPayment(draft, { ...draft.payment, intent: allowed[0] }));
433
471
  }
434
- return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Payment" }) }), _jsxs(CardContent, { className: "space-y-4", children: [allowed.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "No payment methods are available for this booking." })) : (_jsx(RadioGroup, { value: intent, onValueChange: (v) => setDraft(setPayment(draft, { ...draft.payment, intent: v })), className: "grid grid-cols-1 gap-2", children: allowed.map((i) => {
435
- const meta = intentMeta(i);
472
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.payment.title }) }), _jsxs(CardContent, { className: "space-y-4", children: [allowed.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-sm", children: messages.bookingJourney.payment.empty })) : (_jsx(RadioGroup, { value: intent, onValueChange: (v) => setDraft(setPayment(draft, { ...draft.payment, intent: v })), className: "grid grid-cols-1 gap-2", children: allowed.map((i) => {
473
+ const meta = intentMeta(i, messages);
436
474
  const selected = i === intent;
437
475
  return (_jsxs("label", { className: "flex cursor-pointer items-start gap-3 rounded border p-3 text-sm transition-colors " +
438
476
  (selected ? "border-primary bg-primary/5" : "border-input hover:bg-muted/50"), children: [_jsx(RadioGroupItem, { value: i, className: "mt-0.5" }), _jsxs("div", { className: "space-y-0.5", children: [_jsx("div", { className: "font-medium", children: meta.label }), _jsx("div", { className: "text-muted-foreground text-xs", children: meta.description })] })] }, i));
@@ -440,13 +478,14 @@ export function PaymentStep({ draft, setDraft, shape, capabilities, renderProvid
440
478
  intent,
441
479
  schedule: draft.payment.schedule,
442
480
  capabilities,
443
- }) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: "You'll be redirected to our secure payment page after confirming the booking." }))) : null, intent === "bank_transfer" ? _jsx(BankTransferDetails, { capabilities: capabilities }) : null, intent === "inquiry" ? (_jsx("p", { className: "rounded border border-amber-300 bg-amber-50 p-3 text-amber-900 text-xs dark:border-amber-700 dark:bg-amber-950 dark:text-amber-100", children: "We'll send your details to the operator without locking inventory or taking payment. They'll get back to you with availability and a quote \u2014 typically within one business day." })) : null] })] }));
481
+ }) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: messages.bookingJourney.payment.redirectedAfterConfirm }))) : null, intent === "bank_transfer" ? _jsx(BankTransferDetails, { capabilities: capabilities }) : null, intent === "inquiry" ? (_jsx("p", { className: "rounded border border-amber-300 bg-amber-50 p-3 text-amber-900 text-xs dark:border-amber-700 dark:bg-amber-950 dark:text-amber-100", children: messages.bookingJourney.payment.inquiryNotice })) : null] })] }));
444
482
  }
445
483
  function BankTransferDetails({ capabilities, }) {
484
+ const messages = useBookingsUiMessagesOrDefault();
446
485
  const note = capabilities.config?.bankTransferNote;
447
- return (_jsxs("div", { className: "rounded border bg-muted/30 p-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Bank transfer instructions" }), _jsx("p", { className: "text-muted-foreground text-xs", children: typeof note === "string" && note.length > 0
486
+ return (_jsxs("div", { className: "rounded border bg-muted/30 p-3 text-sm", children: [_jsx("p", { className: "font-medium", children: messages.bookingJourney.payment.bankTransferInstructions }), _jsx("p", { className: "text-muted-foreground text-xs", children: typeof note === "string" && note.length > 0
448
487
  ? note
449
- : "After you submit, you'll receive an email with our bank details and a payment reference. Inventory is held pending payment." })] }));
488
+ : messages.bookingJourney.payment.bankTransferDefaultNote })] }));
450
489
  }
451
490
  function isCapabilityEnabled(intent, capabilities) {
452
491
  switch (intent) {
@@ -462,41 +501,19 @@ function isCapabilityEnabled(intent, capabilities) {
462
501
  return capabilities.acceptsInquiry === true;
463
502
  }
464
503
  }
465
- function intentMeta(intent) {
466
- switch (intent) {
467
- case "card":
468
- return {
469
- label: "Pay by card",
470
- description: "Charged immediately. Inventory is reserved on confirmation.",
471
- };
472
- case "bank_transfer":
473
- return {
474
- label: "Bank transfer",
475
- description: "We'll send you bank details and a reference. Inventory is held while we wait for the transfer.",
476
- };
477
- case "hold":
478
- return {
479
- label: "Hold for now",
480
- description: "Reserve inventory without paying. The operator follows up to collect payment.",
481
- };
482
- case "ticket_on_credit":
483
- return {
484
- label: "Agency credit account",
485
- description: "Charge against an agency's credit line. Operator surfaces only.",
486
- };
487
- case "inquiry":
488
- return {
489
- label: "Send as inquiry",
490
- description: "No payment, no inventory hold. The operator gets back to you with availability and a quote.",
491
- };
492
- }
504
+ function intentMeta(intent, messages) {
505
+ return {
506
+ label: messages.bookingJourney.payment.intentLabels[intent],
507
+ description: messages.bookingJourney.payment.intentDescriptions[intent],
508
+ };
493
509
  }
494
510
  // ─────────────────────────────────────────────────────────────────
495
511
  // Review
496
512
  // ─────────────────────────────────────────────────────────────────
497
513
  export function ReviewStep({ draft, setDraft, isCommitting, onConfirm, renderExtras, surface, }) {
514
+ const messages = useBookingsUiMessagesOrDefault();
498
515
  const isPublic = surface === "public";
499
- return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Review & confirm" }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Lead contact" }), _jsxs("div", { className: "text-muted-foreground text-sm", children: [draft.billing.contact.firstName, " ", draft.billing.contact.lastName, " \u2014", " ", draft.billing.contact.email] })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: "Travelers" }), _jsx("ul", { className: "text-muted-foreground text-sm", children: draft.travelers.map((t, i) => (_jsxs("li", { children: [t.firstName, " ", t.lastName, " (", t.band, ")"] }, t.rowId ?? i))) })] }), isPublic ? (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "bj-customer-notes", children: "Notes" }), _jsx(Textarea, { id: "bj-customer-notes", placeholder: "Anything we should know? (allergies, accessibility needs, special occasion\u2026)", value: draft.customerNotes ?? "", onChange: (e) => setDraft({ ...draft, customerNotes: e.target.value }) })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "bj-internal-notes", children: "Internal notes (operator-only)" }), _jsx(Textarea, { id: "bj-internal-notes", value: draft.internalNotes ?? "", onChange: (e) => setDraft({ ...draft, internalNotes: e.target.value }) })] })), renderExtras ? _jsx("div", { children: renderExtras() }) : null, _jsx(Button, { onClick: onConfirm, disabled: isCommitting, children: isCommitting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Confirming\u2026"] })) : ("Confirm booking") })] })] }));
516
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.review.title }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: messages.bookingJourney.review.leadContact }), _jsxs("div", { className: "text-muted-foreground text-sm", children: [draft.billing.contact.firstName, " ", draft.billing.contact.lastName, " \u00B7", " ", draft.billing.contact.email] })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: messages.bookingJourney.review.travelers }), _jsx("ul", { className: "text-muted-foreground text-sm", children: draft.travelers.map((t, i) => (_jsxs("li", { children: [t.firstName, " ", t.lastName, " (", t.band, ")"] }, t.rowId ?? i))) })] }), isPublic ? (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "bj-customer-notes", children: messages.bookingJourney.review.customerNotes }), _jsx(Textarea, { id: "bj-customer-notes", placeholder: messages.bookingJourney.review.customerNotesPlaceholder, value: draft.customerNotes ?? "", onChange: (e) => setDraft({ ...draft, customerNotes: e.target.value }) })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "bj-internal-notes", children: messages.bookingJourney.review.internalNotes }), _jsx(Textarea, { id: "bj-internal-notes", value: draft.internalNotes ?? "", onChange: (e) => setDraft({ ...draft, internalNotes: e.target.value }) })] })), renderExtras ? _jsx("div", { children: renderExtras() }) : null, _jsx(Button, { onClick: onConfirm, disabled: isCommitting, children: isCommitting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), messages.bookingJourney.review.confirming] })) : (messages.bookingJourney.review.confirmBooking) })] })] }));
500
517
  }
501
518
  // ─────────────────────────────────────────────────────────────────
502
519
  // Helpers
@@ -534,7 +551,8 @@ function DateField({ id, label, value, onChange, range = "future", }) {
534
551
  return (_jsxs("div", { className: "space-y-1", id: id, children: [_jsx(Label, { children: label }), _jsx(DatePicker, { value: value || null, onChange: (v) => onChange(v ?? ""), captionLayout: "dropdown", startMonth: startMonth, endMonth: endMonth, defaultMonth: defaultMonth, displayFormat: "PPP" })] }));
535
552
  }
536
553
  function SelectField({ id, label, value, options, onChange, }) {
537
- return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsxs("select", { id: id, className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", value: value, onChange: (e) => onChange(e.target.value), children: [_jsx("option", { value: "", children: "Select\u2026" }), options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] })] }));
554
+ const messages = useBookingsUiMessagesOrDefault();
555
+ return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsxs("select", { id: id, className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", value: value, onChange: (e) => onChange(e.target.value), children: [_jsx("option", { value: "", children: messages.bookingJourney.values.selectPlaceholder }), options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] })] }));
538
556
  }
539
557
  /**
540
558
  * Years between an ISO date-of-birth and today. Returns `null` for
@@ -552,15 +570,19 @@ function computeAge(dob) {
552
570
  age -= 1;
553
571
  return age >= 0 ? age : null;
554
572
  }
555
- function ageHint(min, max) {
556
- if (min != null && max != null)
557
- return `${min}–${max}y`;
558
- if (min != null)
559
- return `${min}y+`;
560
- if (max != null)
561
- return `up to ${max}y`;
573
+ function ageHint(min, max, messages) {
574
+ if (min != null && max != null) {
575
+ return formatMessage(messages.bookingJourney.configure.ageHintRange, { min, max });
576
+ }
577
+ if (min != null) {
578
+ return formatMessage(messages.bookingJourney.configure.ageHintMinimum, { min });
579
+ }
580
+ if (max != null) {
581
+ return formatMessage(messages.bookingJourney.configure.ageHintMaximum, { max });
582
+ }
562
583
  return "";
563
584
  }
585
+ // i18n-literal-ok Generic helper type signature, not user-visible copy.
564
586
  function bucketBy(items, keyFn) {
565
587
  const map = new Map();
566
588
  for (const item of items) {
@@ -1 +1 @@
1
- {"version":3,"file":"side-panel.d.ts","sourceRoot":"","sources":["../../../src/journey/components/side-panel.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,aAAa,CAAA;AAEpF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,SAAS,EACT,aAAa,EACb,aAAa,EACb,WAAW,EACX,KAAK,EACL,KAAK,EACL,SAAS,GACV,EAAE,cAAc,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,CAsD9D"}
1
+ {"version":3,"file":"side-panel.d.ts","sourceRoot":"","sources":["../../../src/journey/components/side-panel.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,aAAa,CAAA;AAEpF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,SAAS,EACT,aAAa,EACb,aAAa,EACb,WAAW,EACX,KAAK,EACL,KAAK,EACL,SAAS,GACV,EAAE,cAAc,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,CAuD9D"}