@voyantjs/bookings-ui 0.107.0 → 0.108.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 (67) hide show
  1. package/dist/components/option-units-stepper-section.d.ts +9 -1
  2. package/dist/components/option-units-stepper-section.d.ts.map +1 -1
  3. package/dist/components/option-units-stepper-section.js +10 -2
  4. package/dist/components/person-picker-section.d.ts +7 -1
  5. package/dist/components/person-picker-section.d.ts.map +1 -1
  6. package/dist/components/person-picker-section.js +2 -2
  7. package/dist/i18n/en.d.ts +37 -1
  8. package/dist/i18n/en.d.ts.map +1 -1
  9. package/dist/i18n/en.js +40 -4
  10. package/dist/i18n/messages.d.ts +37 -1
  11. package/dist/i18n/messages.d.ts.map +1 -1
  12. package/dist/i18n/provider.d.ts +74 -2
  13. package/dist/i18n/provider.d.ts.map +1 -1
  14. package/dist/i18n/ro.d.ts +37 -1
  15. package/dist/i18n/ro.d.ts.map +1 -1
  16. package/dist/i18n/ro.js +39 -3
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -1
  20. package/dist/journey/components/booking-journey.d.ts.map +1 -1
  21. package/dist/journey/components/booking-journey.js +270 -27
  22. package/dist/journey/components/journey-steps/accommodation-step.d.ts +3 -0
  23. package/dist/journey/components/journey-steps/accommodation-step.d.ts.map +1 -0
  24. package/dist/journey/components/journey-steps/accommodation-step.js +71 -0
  25. package/dist/journey/components/journey-steps/addons-step.d.ts +3 -0
  26. package/dist/journey/components/journey-steps/addons-step.d.ts.map +1 -0
  27. package/dist/journey/components/journey-steps/addons-step.js +40 -0
  28. package/dist/journey/components/journey-steps/billing-step.d.ts +8 -0
  29. package/dist/journey/components/journey-steps/billing-step.d.ts.map +1 -0
  30. package/dist/journey/components/journey-steps/billing-step.js +78 -0
  31. package/dist/journey/components/journey-steps/configure-steps.d.ts +28 -0
  32. package/dist/journey/components/journey-steps/configure-steps.d.ts.map +1 -0
  33. package/dist/journey/components/journey-steps/configure-steps.js +231 -0
  34. package/dist/journey/components/journey-steps/documents-step.d.ts +11 -0
  35. package/dist/journey/components/journey-steps/documents-step.d.ts.map +1 -0
  36. package/dist/journey/components/journey-steps/documents-step.js +36 -0
  37. package/dist/journey/components/journey-steps/payment-step.d.ts +29 -0
  38. package/dist/journey/components/journey-steps/payment-step.d.ts.map +1 -0
  39. package/dist/journey/components/journey-steps/payment-step.js +224 -0
  40. package/dist/journey/components/journey-steps/review-step.d.ts +27 -0
  41. package/dist/journey/components/journey-steps/review-step.d.ts.map +1 -0
  42. package/dist/journey/components/journey-steps/review-step.js +18 -0
  43. package/dist/journey/components/journey-steps/shared.d.ts +75 -0
  44. package/dist/journey/components/journey-steps/shared.d.ts.map +1 -0
  45. package/dist/journey/components/journey-steps/shared.js +108 -0
  46. package/dist/journey/components/journey-steps/travelers-step.d.ts +7 -0
  47. package/dist/journey/components/journey-steps/travelers-step.d.ts.map +1 -0
  48. package/dist/journey/components/journey-steps/travelers-step.js +201 -0
  49. package/dist/journey/components/journey-steps.d.ts +13 -39
  50. package/dist/journey/components/journey-steps.d.ts.map +1 -1
  51. package/dist/journey/components/journey-steps.js +16 -613
  52. package/dist/journey/components/side-panel.d.ts +7 -2
  53. package/dist/journey/components/side-panel.d.ts.map +1 -1
  54. package/dist/journey/components/side-panel.js +73 -24
  55. package/dist/journey/index.d.ts +2 -2
  56. package/dist/journey/index.d.ts.map +1 -1
  57. package/dist/journey/index.js +1 -1
  58. package/dist/journey/lib/pax-band-dependencies.d.ts +27 -0
  59. package/dist/journey/lib/pax-band-dependencies.d.ts.map +1 -0
  60. package/dist/journey/lib/pax-band-dependencies.js +50 -0
  61. package/dist/journey/lib/payment-schedule.d.ts +19 -0
  62. package/dist/journey/lib/payment-schedule.d.ts.map +1 -0
  63. package/dist/journey/lib/payment-schedule.js +90 -0
  64. package/dist/journey/types.d.ts +141 -8
  65. package/dist/journey/types.d.ts.map +1 -1
  66. package/dist/journey/types.js +3 -1
  67. package/package.json +32 -32
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Separator } from "@voyantjs/ui/components";
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components/card";
5
+ import { CountryCombobox } from "@voyantjs/ui/components/country-combobox";
6
+ import { Label } from "@voyantjs/ui/components/label";
7
+ import { RadioGroup, RadioGroupItem } from "@voyantjs/ui/components/radio-group";
8
+ import { useBookingsUiMessagesOrDefault } from "../../../i18n/index.js";
9
+ import { patchBilling } from "../../lib/draft-state.js";
10
+ import { Field, JourneyWarnings, PhoneField } from "./shared.js";
11
+ // ─────────────────────────────────────────────────────────────────
12
+ // Billing
13
+ // ─────────────────────────────────────────────────────────────────
14
+ export function BillingStep({ draft, setDraft, renderLeadContactPicker, renderExtras, warnings, }) {
15
+ const messages = useBookingsUiMessagesOrDefault();
16
+ const billing = draft.billing;
17
+ // Merge each partial from the picker (person record, org record, address
18
+ // lookup) into the billing draft without clobbering the other slices.
19
+ const apply = (next) => {
20
+ const patch = {};
21
+ if (next.firstName !== undefined ||
22
+ next.lastName !== undefined ||
23
+ next.email !== undefined ||
24
+ next.phone !== undefined ||
25
+ next.personId !== undefined) {
26
+ patch.contact = {
27
+ ...billing.contact,
28
+ ...(next.firstName !== undefined ? { firstName: next.firstName } : {}),
29
+ ...(next.lastName !== undefined ? { lastName: next.lastName } : {}),
30
+ ...(next.email !== undefined ? { email: next.email } : {}),
31
+ ...(next.phone !== undefined ? { phone: next.phone } : {}),
32
+ ...(next.personId !== undefined ? { personId: next.personId } : {}),
33
+ };
34
+ }
35
+ if (next.organizationId !== undefined) {
36
+ patch.organizationId = next.organizationId;
37
+ }
38
+ if (next.companyName !== undefined || next.taxId !== undefined) {
39
+ patch.company = {
40
+ name: next.companyName ?? billing.company?.name ?? "",
41
+ vatId: next.taxId ?? billing.company?.vatId,
42
+ };
43
+ }
44
+ if (next.address) {
45
+ patch.address = { ...billing.address, ...next.address };
46
+ }
47
+ setDraft(patchBilling(draft, patch));
48
+ };
49
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.billing.title }) }), _jsx(Separator, {}), _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, buyerType: billing.buyerType }) })) : null, renderLeadContactPicker ? null : (_jsxs(_Fragment, { children: [_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, {
50
+ contact: { ...billing.contact, firstName: v },
51
+ })) }), _jsx(Field, { id: "bj-billing-lastName", label: messages.bookingJourney.billing.lastName, value: billing.contact.lastName, onChange: (v) => setDraft(patchBilling(draft, {
52
+ contact: { ...billing.contact, lastName: v },
53
+ })) }), _jsx(Field, { id: "bj-billing-email", label: messages.bookingJourney.billing.email, type: "email", value: billing.contact.email, onChange: (v) => setDraft(patchBilling(draft, {
54
+ contact: { ...billing.contact, email: v },
55
+ })) }), _jsx(PhoneField, { id: "bj-billing-phone", label: messages.bookingJourney.billing.phone, value: billing.contact.phone ?? "", onChange: (v) => setDraft(patchBilling(draft, {
56
+ contact: { ...billing.contact, phone: v },
57
+ })) })] }), _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, {
58
+ address: { ...billing.address, line1: v },
59
+ })) }), _jsx(Field, { id: "bj-billing-line2", label: messages.bookingJourney.billing.addressLine2Optional, value: billing.address.line2 ?? "", onChange: (v) => setDraft(patchBilling(draft, {
60
+ address: { ...billing.address, line2: v },
61
+ })) }), _jsx(Field, { id: "bj-billing-city", label: messages.bookingJourney.billing.city, value: billing.address.city ?? "", onChange: (v) => setDraft(patchBilling(draft, {
62
+ address: { ...billing.address, city: v },
63
+ })) }), _jsx(Field, { id: "bj-billing-postal", label: messages.bookingJourney.billing.postalCode, value: billing.address.postal ?? "", onChange: (v) => setDraft(patchBilling(draft, {
64
+ address: { ...billing.address, postal: v },
65
+ })) }), _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, {
66
+ address: { ...billing.address, country: code ?? "" },
67
+ })) })] })] }), 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, {
68
+ company: {
69
+ ...(billing.company ?? { name: "" }),
70
+ name: v,
71
+ },
72
+ })) }), _jsx(Field, { id: "bj-billing-vatId", label: messages.bookingJourney.billing.vatId, value: billing.company?.vatId ?? "", onChange: (v) => setDraft(patchBilling(draft, {
73
+ company: {
74
+ ...(billing.company ?? { name: "" }),
75
+ vatId: v,
76
+ },
77
+ })) })] })) : null] })), renderExtras ? _jsx("div", { children: renderExtras() }) : null, _jsx(JourneyWarnings, { warnings: warnings })] })] }));
78
+ }
@@ -0,0 +1,28 @@
1
+ import type { BookingDraftShape } from "@voyantjs/catalog/booking-engine";
2
+ import { type Draft } from "../../lib/draft-state.js";
3
+ import { type RenderDeparturePicker, type RenderUnitsPicker, type StepCommonProps } from "./shared.js";
4
+ export declare function DepartureStep({ draft, setDraft, shape, productId, renderDeparturePicker, }: StepCommonProps & {
5
+ /** Owned product id — passed to the injected departure picker. */
6
+ productId?: string;
7
+ renderDeparturePicker?: RenderDeparturePicker;
8
+ }): React.ReactElement;
9
+ export declare function OptionsStep({ draft, setDraft, shape, productId, renderUnitsPicker, }: StepCommonProps & {
10
+ /** Owned product id — passed to the injected units picker. */
11
+ productId?: string;
12
+ renderUnitsPicker?: RenderUnitsPicker;
13
+ }): React.ReactElement;
14
+ export declare function PaxBands({ draft, setDraft, shape }: StepCommonProps): React.ReactElement;
15
+ export declare function PaxValidation({ draft, shape, }: {
16
+ draft: Draft;
17
+ shape: BookingDraftShape;
18
+ }): React.ReactNode;
19
+ /**
20
+ * Surfaces broken cross-band occupancy rules (e.g. "Child under 6
21
+ * requires an Adult") as hard validation errors under the pax steppers.
22
+ * These also block step advancement via `canAdvanceFromStep`.
23
+ */
24
+ export declare function PaxDependencyWarnings({ draft, shape, }: {
25
+ draft: Draft;
26
+ shape: BookingDraftShape;
27
+ }): React.ReactNode;
28
+ //# sourceMappingURL=configure-steps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure-steps.d.ts","sourceRoot":"","sources":["../../../../src/journey/components/journey-steps/configure-steps.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AASzE,OAAO,EAAE,KAAK,KAAK,EAA2C,MAAM,0BAA0B,CAAA;AAK9F,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAA;AAMpB,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,SAAS,EACT,qBAAqB,GACtB,EAAE,eAAe,GAAG;IACnB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;CAC9C,GAAG,KAAK,CAAC,YAAY,CA0CrB;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,SAAS,EACT,iBAAiB,GAClB,EAAE,eAAe,GAAG;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACtC,GAAG,KAAK,CAAC,YAAY,CAiErB;AAED,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAsDxF;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,GACN,EAAE;IACD,KAAK,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,iBAAiB,CAAA;CACzB,GAAG,KAAK,CAAC,SAAS,CAwBlB;AAgCD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,KAAK,GACN,EAAE;IACD,KAAK,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,iBAAiB,CAAA;CACzB,GAAG,KAAK,CAAC,SAAS,CAoBlB"}
@@ -0,0 +1,231 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Separator } from "@voyantjs/ui/components";
4
+ import { Button } from "@voyantjs/ui/components/button";
5
+ import { Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components/card";
6
+ import { Input } from "@voyantjs/ui/components/input";
7
+ import { Label } from "@voyantjs/ui/components/label";
8
+ import { RadioGroup, RadioGroupItem } from "@voyantjs/ui/components/radio-group";
9
+ import { Minus, Plus } from "lucide-react";
10
+ import { formatMessage, useBookingsUiMessagesOrDefault } from "../../../i18n/index.js";
11
+ import { patchConfigure, patchPaxCount, totalPax } from "../../lib/draft-state.js";
12
+ import { evaluatePaxBandDependencies, } from "../../lib/pax-band-dependencies.js";
13
+ import { ageHint, DateField, } from "./shared.js";
14
+ // ─────────────────────────────────────────────────────────────────
15
+ // Configure
16
+ // ─────────────────────────────────────────────────────────────────
17
+ export function DepartureStep({ draft, setDraft, shape, productId, renderDeparturePicker, }) {
18
+ const messages = useBookingsUiMessagesOrDefault();
19
+ const subSteps = shape.configureSubSteps ?? [];
20
+ // With no descriptor sub-steps, still offer a departure (storefront
21
+ // free-date fallback).
22
+ const showsDeparture = subSteps.length === 0 || subSteps.some((s) => s.kind === "departure");
23
+ const departureNode = showsDeparture ? (renderDeparturePicker && productId ? (renderDeparturePicker({
24
+ productId,
25
+ optionId: draft.configure.variantId ?? null,
26
+ slotId: draft.configure.departureSlotId ?? null,
27
+ departureDate: draft.configure.departureDate ?? null,
28
+ departureTime: draft.configure.departureTime ?? null,
29
+ onChange: (next) => setDraft(patchConfigure(draft, {
30
+ ...(next.slotId !== undefined ? { departureSlotId: next.slotId ?? undefined } : {}),
31
+ ...(next.departureDate !== undefined
32
+ ? { departureDate: next.departureDate ?? undefined }
33
+ : {}),
34
+ ...(next.departureTime !== undefined
35
+ ? { departureTime: next.departureTime ?? undefined }
36
+ : {}),
37
+ })),
38
+ })) : (_jsx(DepartureBasic, { draft: draft, setDraft: setDraft }))) : null;
39
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.steps.departure }) }), _jsx(Separator, {}), _jsx(CardContent, { className: "space-y-6", children: departureNode })] }));
40
+ }
41
+ export function OptionsStep({ draft, setDraft, shape, productId, renderUnitsPicker, }) {
42
+ const messages = useBookingsUiMessagesOrDefault();
43
+ const subSteps = shape.configureSubSteps ?? [];
44
+ const optionList = subSteps.flatMap((s) => (s.kind === "product-option" ? s.options : []));
45
+ const multipleOptions = optionList.length > 1;
46
+ const showsUnits = subSteps.some((s) => s.kind === "option-units");
47
+ const otherSteps = subSteps.filter((s) => s.kind !== "departure" &&
48
+ s.kind !== "product-option" &&
49
+ s.kind !== "option-units" &&
50
+ s.kind !== "occupancy");
51
+ const unitsNode = showsUnits && renderUnitsPicker && productId
52
+ ? renderUnitsPicker({
53
+ productId,
54
+ optionId: draft.configure.variantId ?? null,
55
+ slotId: draft.configure.departureSlotId ?? null,
56
+ selections: draft.configure.optionSelections ?? [],
57
+ onChange: (selections) => setDraft(patchConfigure(draft, { optionSelections: selections })),
58
+ })
59
+ : null;
60
+ const optionNode = optionList.length > 0 ? (_jsx(ProductOptionFields, { draft: draft, setDraft: setDraft, options: optionList,
61
+ // With a real choice between options, nest the rooms under the
62
+ // SELECTED option so switching reveals that option's inventory in
63
+ // place. With a single/no option there's nothing to switch, so the
64
+ // rooms render directly below instead.
65
+ renderSelectedUnits: multipleOptions && unitsNode ? () => unitsNode : undefined })) : null;
66
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.steps.options }) }), _jsx(Separator, {}), _jsxs(CardContent, { className: "space-y-6", children: [optionNode || unitsNode ? (_jsxs("div", { className: "space-y-3", children: [optionNode, unitsNode && !multipleOptions ? unitsNode : null] })) : null, otherSteps.length > 0 ? (_jsx("div", { className: "space-y-4", children: otherSteps.map((sub) => renderOtherConfigureSubStep(sub, draft, setDraft)) })) : null] })] }));
67
+ }
68
+ export function PaxBands({ draft, setDraft, shape }) {
69
+ const messages = useBookingsUiMessagesOrDefault();
70
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: messages.bookingJourney.travelers.partySize }), _jsx("div", { className: "flex flex-col gap-2", children: shape.paxBands.map((band) => {
71
+ const value = draft.configure.pax?.[band.code] ?? 0;
72
+ return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border px-3 py-2", children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm 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: "ghost", size: "sm", type: "button", className: "h-7 w-7 p-0", disabled: value <= band.minCount, onClick: () => setDraft(patchPaxCount(draft, band.code, value - 1)), "aria-label": formatMessage(messages.bookingJourney.travelers.decrease, {
73
+ label: band.label,
74
+ }), children: _jsx(Minus, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "min-w-[1.5rem] text-center text-sm tabular-nums", children: value }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", className: "h-7 w-7 p-0", disabled: value >= band.maxCount, onClick: () => setDraft(patchPaxCount(draft, band.code, value + 1)), "aria-label": formatMessage(messages.bookingJourney.travelers.increase, {
75
+ label: band.label,
76
+ }), children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })] }, band.code));
77
+ }) }), _jsx(PaxValidation, { draft: draft, shape: shape })] }));
78
+ }
79
+ export function PaxValidation({ draft, shape, }) {
80
+ const messages = useBookingsUiMessagesOrDefault();
81
+ const total = totalPax(draft);
82
+ const { min, max } = shape.paxBandsAllowedTotal;
83
+ if (total < min) {
84
+ return (_jsx("p", { className: "text-sm text-amber-600", children: formatMessage(messages.bookingJourney.validation.addAtLeastTravelers, {
85
+ count: min,
86
+ plural: min === 1 ? "" : "s",
87
+ }) }));
88
+ }
89
+ if (total > max) {
90
+ return (_jsx("p", { className: "text-sm text-destructive", children: formatMessage(messages.bookingJourney.validation.maxTravelersPerBooking, {
91
+ count: max,
92
+ }) }));
93
+ }
94
+ return null;
95
+ }
96
+ /** Formats one occupancy-rule violation into a localized message. */
97
+ function formatPaxDependencyViolation(violation, messages) {
98
+ switch (violation.type) {
99
+ case "requires":
100
+ return formatMessage(messages.dependencyRequires, {
101
+ dependent: violation.dependentLabel,
102
+ master: violation.masterLabel,
103
+ });
104
+ case "excludes":
105
+ return formatMessage(messages.dependencyExcludes, {
106
+ dependent: violation.dependentLabel,
107
+ master: violation.masterLabel,
108
+ });
109
+ case "limits_per_master":
110
+ return formatMessage(messages.dependencyLimitPerMaster, {
111
+ limit: violation.limit ?? 0,
112
+ dependent: violation.dependentLabel,
113
+ master: violation.masterLabel,
114
+ });
115
+ case "limits_sum":
116
+ return formatMessage(messages.dependencyLimitSum, {
117
+ limit: violation.limit ?? 0,
118
+ dependent: violation.dependentLabel,
119
+ });
120
+ }
121
+ }
122
+ /**
123
+ * Surfaces broken cross-band occupancy rules (e.g. "Child under 6
124
+ * requires an Adult") as hard validation errors under the pax steppers.
125
+ * These also block step advancement via `canAdvanceFromStep`.
126
+ */
127
+ export function PaxDependencyWarnings({ draft, shape, }) {
128
+ const messages = useBookingsUiMessagesOrDefault();
129
+ const violations = evaluatePaxBandDependencies(draft.configure.pax, shape.paxBandDependencies, shape.paxBands);
130
+ if (violations.length === 0)
131
+ return null;
132
+ return (_jsx("div", { className: "space-y-1", children: violations.map((violation) => (_jsx("p", { className: "text-destructive text-sm", children: formatPaxDependencyViolation(violation, messages.bookingJourney.validation) }, `${violation.type}-${violation.dependentCode}-${violation.masterCode}`))) }));
133
+ }
134
+ /**
135
+ * Renders the vertical-specific Configure sub-steps that aren't part of the
136
+ * fixed products layout (departure / option / rooms / occupancy are handled
137
+ * explicitly in `ConfigureStep`). Cruise cabins, date ranges, and air
138
+ * arrangement land here, in descriptor order.
139
+ */
140
+ function renderOtherConfigureSubStep(sub, draft, setDraft) {
141
+ if (sub.kind === "date-range") {
142
+ return (_jsx(DateRangeFields, { draft: draft, setDraft: setDraft, minNights: sub.minNights, maxNights: sub.maxNights }, "date-range"));
143
+ }
144
+ if (sub.kind === "cabin-category") {
145
+ return (_jsx(CabinCategoryFields, { draft: draft, setDraft: setDraft, categories: sub.categories }, "cabin-category"));
146
+ }
147
+ if (sub.kind === "cabin-number") {
148
+ return (_jsx(CabinNumberFields, { draft: draft, setDraft: setDraft, perCategory: sub.perCategory }, "cabin-number"));
149
+ }
150
+ if (sub.kind === "air-arrangement") {
151
+ return _jsx(AirArrangementFields, { draft: draft, setDraft: setDraft }, "air-arrangement");
152
+ }
153
+ return null;
154
+ }
155
+ function ProductOptionFields({ draft, setDraft, options, renderSelectedUnits, }) {
156
+ const messages = useBookingsUiMessagesOrDefault();
157
+ const selectedId = draft.configure.variantId;
158
+ if (options.length === 0)
159
+ return null;
160
+ return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: messages.bookingJourney.configure.option }), _jsx(RadioGroup, { value: selectedId ?? "",
161
+ // Switching options must clear the previous option's room/unit picks —
162
+ // otherwise stale `optionSelections` from option A still drive the
163
+ // quote/commit (price, item lines, rooms gate) under option B.
164
+ onValueChange: (v) => setDraft(patchConfigure(draft, v === selectedId ? { variantId: v } : { variantId: v, optionSelections: [] })), className: "grid grid-cols-1 gap-2", children: options.map((option) => {
165
+ const selected = option.id === selectedId;
166
+ return (_jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "flex cursor-pointer items-start gap-3 rounded-md border p-3 text-sm transition-colors " +
167
+ (selected ? "border-primary bg-primary/5" : "border-input hover:bg-muted/50"), children: [_jsx(RadioGroupItem, { value: option.id, className: "mt-0.5" }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "font-medium", children: [option.name, option.code ? (_jsx("span", { className: "ml-2 text-muted-foreground text-xs uppercase", children: option.code })) : null] }), option.description ? (_jsx("div", { className: "mt-1 text-muted-foreground text-xs", children: option.description })) : null] })] }), selected && renderSelectedUnits ? (_jsx("div", { className: "ml-7 space-y-2 border-muted border-l-2 pl-4", children: renderSelectedUnits() })) : null] }, option.id));
168
+ }) })] }));
169
+ }
170
+ function DepartureBasic({ draft, setDraft, }) {
171
+ const messages = useBookingsUiMessagesOrDefault();
172
+ 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 })) })] })] }));
173
+ }
174
+ function DateRangeFields({ draft, setDraft, minNights, maxNights, }) {
175
+ const messages = useBookingsUiMessagesOrDefault();
176
+ const range = draft.configure.dateRange;
177
+ 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, {
178
+ dateRange: { checkIn: v, checkOut: range?.checkOut ?? "" },
179
+ })), range: "future" }), _jsx(DateField, { id: "bj-checkout", label: formatMessage(messages.bookingJourney.configure.checkOutWithNights, {
180
+ minNights,
181
+ maxNights,
182
+ }), value: range?.checkOut ?? "", onChange: (v) => setDraft(patchConfigure(draft, {
183
+ dateRange: { checkIn: range?.checkIn ?? "", checkOut: v },
184
+ })), range: "future" })] }));
185
+ }
186
+ function CabinCategoryFields({ draft, setDraft, categories, }) {
187
+ const messages = useBookingsUiMessagesOrDefault();
188
+ 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) => {
189
+ const selected = draft.configure.cabinCategoryId === cat.id;
190
+ return (_jsxs("button", { type: "button", className: `rounded-md border p-3 text-left ${selected ? "border-primary ring-2 ring-primary" : ""}`, onClick: () => setDraft(patchConfigure(draft, {
191
+ cabinCategoryId: cat.id,
192
+ cabinNumberId: undefined,
193
+ })), 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));
194
+ }) })] }));
195
+ }
196
+ function AirArrangementFields({ draft, setDraft, }) {
197
+ const messages = useBookingsUiMessagesOrDefault();
198
+ const current = draft.configure.airArrangement;
199
+ const options = [
200
+ {
201
+ value: "cruise_line",
202
+ label: messages.bookingJourney.configure.airOptions.cruise_line.label,
203
+ description: messages.bookingJourney.configure.airOptions.cruise_line.description,
204
+ },
205
+ {
206
+ value: "independent",
207
+ label: messages.bookingJourney.configure.airOptions.independent.label,
208
+ description: messages.bookingJourney.configure.airOptions.independent.description,
209
+ },
210
+ {
211
+ value: "none",
212
+ label: messages.bookingJourney.configure.airOptions.none.label,
213
+ description: messages.bookingJourney.configure.airOptions.none.description,
214
+ },
215
+ ];
216
+ 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) => {
217
+ const selected = current === opt.value;
218
+ return (_jsxs("button", { type: "button", className: `rounded-md 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));
219
+ }) })] }));
220
+ }
221
+ function CabinNumberFields({ draft, setDraft, perCategory, }) {
222
+ const messages = useBookingsUiMessagesOrDefault();
223
+ const catId = draft.configure.cabinCategoryId;
224
+ if (!catId)
225
+ return null;
226
+ const cabins = perCategory[catId] ?? [];
227
+ 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) => {
228
+ const selected = draft.configure.cabinNumberId === cabin.id;
229
+ return (_jsx("button", { type: "button", className: `rounded-md border p-2 text-sm ${selected ? "border-primary ring-2 ring-primary" : ""}`, onClick: () => setDraft(patchConfigure(draft, { cabinNumberId: cabin.id })), children: cabin.label }, cabin.id));
230
+ }) })] }));
231
+ }
@@ -0,0 +1,11 @@
1
+ import type { Draft } from "../../lib/draft-state.js";
2
+ /**
3
+ * Operator-only finalization that isn't about payment: an internal note and the
4
+ * document-generation settings (proforma vs invoice+contract, notify). Split
5
+ * out of the Payment block since these don't affect the amount due.
6
+ */
7
+ export declare function DocumentsStep({ draft, setDraft, }: {
8
+ draft: Draft;
9
+ setDraft: (next: Draft) => void;
10
+ }): React.ReactElement;
11
+ //# sourceMappingURL=documents-step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents-step.d.ts","sourceRoot":"","sources":["../../../../src/journey/components/journey-steps/documents-step.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAErD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAA;CAChC,GAAG,KAAK,CAAC,YAAY,CA6GrB"}
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components/card";
4
+ import { Checkbox } from "@voyantjs/ui/components/checkbox";
5
+ import { Label } from "@voyantjs/ui/components/label";
6
+ import { Separator } from "@voyantjs/ui/components/separator";
7
+ import { Textarea } from "@voyantjs/ui/components/textarea";
8
+ import { useBookingsUiMessagesOrDefault } from "../../../i18n/index.js";
9
+ /**
10
+ * Operator-only finalization that isn't about payment: an internal note and the
11
+ * document-generation settings (proforma vs invoice+contract, notify). Split
12
+ * out of the Payment block since these don't affect the amount due.
13
+ */
14
+ export function DocumentsStep({ draft, setDraft, }) {
15
+ const messages = useBookingsUiMessagesOrDefault();
16
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: messages.bookingJourney.steps.documents }) }), _jsx(Separator, {}), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 text-sm", children: [_jsx(Checkbox, { id: "bj-save-as-draft", checked: draft.saveAsDraft === true, onCheckedChange: (v) => setDraft({ ...draft, saveAsDraft: v === true }), className: "mt-0.5" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { htmlFor: "bj-save-as-draft", className: "cursor-pointer", children: messages.bookingJourney.documents.saveAsDraft }), _jsx("p", { className: "text-muted-foreground text-xs", children: messages.bookingJourney.documents.saveAsDraftHint })] })] }), _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 }) })] }), _jsxs("div", { className: "flex flex-col gap-3 rounded-md border p-3", children: [_jsx(Label, { children: messages.bookingCreateDialog.labels.documentGenerationHeading }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { id: "bj-doc-proforma", checked: draft.documentGeneration?.invoiceType === "proforma", onCheckedChange: (v) => setDraft({
17
+ ...draft,
18
+ documentGeneration: v === true
19
+ ? {
20
+ contractDocument: false,
21
+ invoiceDocument: true,
22
+ invoiceType: "proforma",
23
+ }
24
+ : undefined,
25
+ }) }), _jsx(Label, { htmlFor: "bj-doc-proforma", className: "cursor-pointer", children: messages.bookingCreateDialog.labels.generateProforma })] }), _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Checkbox, { id: "bj-doc-invoice-contract", checked: draft.documentGeneration?.contractDocument === true &&
26
+ draft.documentGeneration?.invoiceType !== "proforma", onCheckedChange: (v) => setDraft({
27
+ ...draft,
28
+ documentGeneration: v === true
29
+ ? {
30
+ contractDocument: true,
31
+ invoiceDocument: true,
32
+ invoiceType: "invoice",
33
+ }
34
+ : undefined,
35
+ }) }), _jsx(Label, { htmlFor: "bj-doc-invoice-contract", className: "cursor-pointer", children: messages.bookingCreateDialog.labels.generateInvoiceAndContract })] }), _jsxs("div", { className: "flex items-start gap-2 border-t pt-2 text-sm", children: [_jsx(Checkbox, { id: "bj-notify-traveler", checked: draft.suppressNotifications !== true, onCheckedChange: (v) => setDraft({ ...draft, suppressNotifications: v !== true }) }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { htmlFor: "bj-notify-traveler", className: "cursor-pointer", children: messages.bookingCreateDialog.fields.notifyTraveler }), _jsx("p", { className: "text-muted-foreground text-xs", children: messages.bookingCreateDialog.fields.notifyTravelerHint })] })] })] })] })] })] }));
36
+ }
@@ -0,0 +1,29 @@
1
+ import { type Draft } from "../../lib/draft-state.js";
2
+ import type { PaymentProviderCapabilities, PaymentProviderStepRenderProps, VoucherPickerProps } from "../../types.js";
3
+ import type { StepCommonProps } from "./shared.js";
4
+ export declare function PaymentStep({ draft, setDraft, shape, capabilities, renderProviderStep, surface, pricing, }: StepCommonProps & {
5
+ capabilities: PaymentProviderCapabilities;
6
+ renderProviderStep?: (props: PaymentProviderStepRenderProps) => React.ReactNode;
7
+ surface?: "admin" | "public";
8
+ /** Live quote total + currency — drives the payment-schedule editor defaults. */
9
+ pricing?: {
10
+ total: number;
11
+ currency: string;
12
+ } | null;
13
+ }): React.ReactElement;
14
+ /**
15
+ * Operator-only PAYMENT-RELATED finalize controls — manual price override and
16
+ * voucher redemption (both change the amount due, so they live in the Payment
17
+ * block). Non-payment finalization (internal notes, document generation) lives
18
+ * in the separate Documents step.
19
+ */
20
+ export declare function FinalizeControls({ draft, setDraft, pricing, renderVoucherPicker, }: {
21
+ draft: Draft;
22
+ setDraft: (next: Draft) => void;
23
+ pricing?: {
24
+ total: number;
25
+ currency: string;
26
+ } | null;
27
+ renderVoucherPicker?: (props: VoucherPickerProps) => React.ReactNode;
28
+ }): React.ReactElement;
29
+ //# sourceMappingURL=payment-step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-step.d.ts","sourceRoot":"","sources":["../../../../src/journey/components/journey-steps/payment-step.tsx"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,KAAK,EAAc,MAAM,0BAA0B,CAAA;AAKjE,OAAO,KAAK,EACV,2BAA2B,EAC3B,8BAA8B,EAC9B,kBAAkB,EACnB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAMlD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,YAAY,EACZ,kBAAkB,EAClB,OAAO,EACP,OAAO,GACR,EAAE,eAAe,GAAG;IACnB,YAAY,EAAE,2BAA2B,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,KAAK,CAAC,SAAS,CAAA;IAC/E,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAC5B,iFAAiF;IACjF,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACrD,GAAG,KAAK,CAAC,YAAY,CAqIrB;AAiSD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,mBAAmB,GACpB,EAAE;IACD,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAA;IAC/B,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACpD,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAA;CACrE,GAAG,KAAK,CAAC,YAAY,CAerB"}