@voyantjs/bookings-ui 0.13.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 (101) hide show
  1. package/README.md +13 -0
  2. package/dist/components/booking-activity-timeline.d.ts +5 -0
  3. package/dist/components/booking-activity-timeline.d.ts.map +1 -0
  4. package/dist/components/booking-activity-timeline.js +83 -0
  5. package/dist/components/booking-cancellation-dialog.d.ts +18 -0
  6. package/dist/components/booking-cancellation-dialog.d.ts.map +1 -0
  7. package/dist/components/booking-cancellation-dialog.js +80 -0
  8. package/dist/components/booking-create-dialog.d.ts +21 -0
  9. package/dist/components/booking-create-dialog.d.ts.map +1 -0
  10. package/dist/components/booking-create-dialog.js +313 -0
  11. package/dist/components/booking-dialog.d.ts +23 -0
  12. package/dist/components/booking-dialog.d.ts.map +1 -0
  13. package/dist/components/booking-dialog.js +108 -0
  14. package/dist/components/booking-document-dialog.d.ts +8 -0
  15. package/dist/components/booking-document-dialog.d.ts.map +1 -0
  16. package/dist/components/booking-document-dialog.js +67 -0
  17. package/dist/components/booking-document-list.d.ts +5 -0
  18. package/dist/components/booking-document-list.d.ts.map +1 -0
  19. package/dist/components/booking-document-list.js +38 -0
  20. package/dist/components/booking-group-link-dialog.d.ts +10 -0
  21. package/dist/components/booking-group-link-dialog.d.ts.map +1 -0
  22. package/dist/components/booking-group-link-dialog.js +68 -0
  23. package/dist/components/booking-group-section.d.ts +17 -0
  24. package/dist/components/booking-group-section.d.ts.map +1 -0
  25. package/dist/components/booking-group-section.js +31 -0
  26. package/dist/components/booking-guarantee-dialog.d.ts +10 -0
  27. package/dist/components/booking-guarantee-dialog.d.ts.map +1 -0
  28. package/dist/components/booking-guarantee-dialog.js +101 -0
  29. package/dist/components/booking-guarantee-list.d.ts +5 -0
  30. package/dist/components/booking-guarantee-list.d.ts.map +1 -0
  31. package/dist/components/booking-guarantee-list.js +45 -0
  32. package/dist/components/booking-item-dialog.d.ts +10 -0
  33. package/dist/components/booking-item-dialog.d.ts.map +1 -0
  34. package/dist/components/booking-item-dialog.js +119 -0
  35. package/dist/components/booking-item-list.d.ts +5 -0
  36. package/dist/components/booking-item-list.d.ts.map +1 -0
  37. package/dist/components/booking-item-list.js +50 -0
  38. package/dist/components/booking-item-travelers.d.ts +6 -0
  39. package/dist/components/booking-item-travelers.d.ts.map +1 -0
  40. package/dist/components/booking-item-travelers.js +50 -0
  41. package/dist/components/booking-list.d.ts +7 -0
  42. package/dist/components/booking-list.d.ts.map +1 -0
  43. package/dist/components/booking-list.js +47 -0
  44. package/dist/components/booking-notes.d.ts +5 -0
  45. package/dist/components/booking-notes.d.ts.map +1 -0
  46. package/dist/components/booking-notes.js +16 -0
  47. package/dist/components/booking-payment-schedule-dialog.d.ts +10 -0
  48. package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -0
  49. package/dist/components/booking-payment-schedule-dialog.js +77 -0
  50. package/dist/components/booking-payment-schedule-list.d.ts +5 -0
  51. package/dist/components/booking-payment-schedule-list.d.ts.map +1 -0
  52. package/dist/components/booking-payment-schedule-list.js +43 -0
  53. package/dist/components/booking-payments-summary.d.ts +5 -0
  54. package/dist/components/booking-payments-summary.d.ts.map +1 -0
  55. package/dist/components/booking-payments-summary.js +19 -0
  56. package/dist/components/file-dropzone.d.ts +25 -0
  57. package/dist/components/file-dropzone.d.ts.map +1 -0
  58. package/dist/components/file-dropzone.js +92 -0
  59. package/dist/components/passengers-section.d.ts +72 -0
  60. package/dist/components/passengers-section.d.ts.map +1 -0
  61. package/dist/components/passengers-section.js +74 -0
  62. package/dist/components/payment-schedule-section.d.ts +62 -0
  63. package/dist/components/payment-schedule-section.d.ts.map +1 -0
  64. package/dist/components/payment-schedule-section.js +88 -0
  65. package/dist/components/person-picker-section.d.ts +53 -0
  66. package/dist/components/person-picker-section.d.ts.map +1 -0
  67. package/dist/components/person-picker-section.js +71 -0
  68. package/dist/components/price-breakdown-section.d.ts +48 -0
  69. package/dist/components/price-breakdown-section.d.ts.map +1 -0
  70. package/dist/components/price-breakdown-section.js +165 -0
  71. package/dist/components/product-picker-section.d.ts +27 -0
  72. package/dist/components/product-picker-section.d.ts.map +1 -0
  73. package/dist/components/product-picker-section.js +41 -0
  74. package/dist/components/rooms-stepper-section.d.ts +45 -0
  75. package/dist/components/rooms-stepper-section.d.ts.map +1 -0
  76. package/dist/components/rooms-stepper-section.js +60 -0
  77. package/dist/components/shared-room-section.d.ts +37 -0
  78. package/dist/components/shared-room-section.d.ts.map +1 -0
  79. package/dist/components/shared-room-section.js +40 -0
  80. package/dist/components/status-change-dialog.d.ts +10 -0
  81. package/dist/components/status-change-dialog.d.ts.map +1 -0
  82. package/dist/components/status-change-dialog.js +41 -0
  83. package/dist/components/supplier-status-dialog.d.ts +10 -0
  84. package/dist/components/supplier-status-dialog.d.ts.map +1 -0
  85. package/dist/components/supplier-status-dialog.js +77 -0
  86. package/dist/components/supplier-status-list.d.ts +5 -0
  87. package/dist/components/supplier-status-list.d.ts.map +1 -0
  88. package/dist/components/supplier-status-list.js +33 -0
  89. package/dist/components/traveler-dialog.d.ts +10 -0
  90. package/dist/components/traveler-dialog.d.ts.map +1 -0
  91. package/dist/components/traveler-dialog.js +64 -0
  92. package/dist/components/traveler-list.d.ts +5 -0
  93. package/dist/components/traveler-list.d.ts.map +1 -0
  94. package/dist/components/traveler-list.js +32 -0
  95. package/dist/components/voucher-picker-section.d.ts +50 -0
  96. package/dist/components/voucher-picker-section.d.ts.map +1 -0
  97. package/dist/components/voucher-picker-section.js +94 -0
  98. package/dist/index.d.ts +33 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +32 -0
  101. package/package.json +76 -0
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useSupplierStatuses } from "@voyantjs/bookings-react";
4
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle, } from "@voyantjs/voyant-ui/components";
5
+ import { Pencil, Plus } from "lucide-react";
6
+ import * as React from "react";
7
+ import { SupplierStatusDialog } from "./supplier-status-dialog";
8
+ const supplierStatusVariant = {
9
+ pending: "outline",
10
+ confirmed: "default",
11
+ rejected: "destructive",
12
+ cancelled: "secondary",
13
+ };
14
+ export function SupplierStatusList({ bookingId }) {
15
+ const [dialogOpen, setDialogOpen] = React.useState(false);
16
+ const [editing, setEditing] = React.useState(undefined);
17
+ const { data } = useSupplierStatuses(bookingId);
18
+ const statuses = data?.data ?? [];
19
+ return (_jsxs(Card, { "data-slot": "supplier-status-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { children: "Supplier Confirmations" }), _jsxs(Button, { size: "sm", onClick: () => {
20
+ setEditing(undefined);
21
+ setDialogOpen(true);
22
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Add Supplier"] })] }), _jsx(CardContent, { children: statuses.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No supplier statuses yet." })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: "Service" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Status" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Cost" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Reference" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Confirmed" }), _jsx("th", { className: "w-12 p-2" })] }) }), _jsx("tbody", { children: statuses.map((status) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2", children: status.serviceName }), _jsx("td", { className: "p-2", children: _jsx(Badge, { variant: supplierStatusVariant[status.status] ?? "secondary", className: "capitalize text-xs", children: status.status }) }), _jsxs("td", { className: "p-2 font-mono", children: [(status.costAmountCents / 100).toFixed(2), " ", status.costCurrency] }), _jsx("td", { className: "p-2", children: status.supplierReference ?? "-" }), _jsx("td", { className: "p-2", children: status.confirmedAt ? new Date(status.confirmedAt).toLocaleDateString() : "-" }), _jsx("td", { className: "p-2", children: _jsx("button", { type: "button", onClick: () => {
23
+ setEditing(status);
24
+ setDialogOpen(true);
25
+ }, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }) })] }, status.id))) })] }) })) }), _jsx(SupplierStatusDialog, { open: dialogOpen, onOpenChange: (nextOpen) => {
26
+ setDialogOpen(nextOpen);
27
+ if (!nextOpen) {
28
+ setEditing(undefined);
29
+ }
30
+ }, bookingId: bookingId, supplierStatus: editing, onSuccess: () => {
31
+ setEditing(undefined);
32
+ } })] }));
33
+ }
@@ -0,0 +1,10 @@
1
+ import { type BookingTravelerRecord } from "@voyantjs/bookings-react";
2
+ export interface TravelerDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ bookingId: string;
6
+ traveler?: BookingTravelerRecord;
7
+ onSuccess?: () => void;
8
+ }
9
+ export declare function TravelerDialog({ open, onOpenChange, bookingId, traveler, onSuccess, }: TravelerDialogProps): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=traveler-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traveler-dialog.d.ts","sourceRoot":"","sources":["../../src/components/traveler-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,qBAAqB,EAAuB,MAAM,0BAA0B,CAAA;AA8B1F,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,qBAAqB,CAAA;IAChC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,SAAS,GACV,EAAE,mBAAmB,2CAmHrB"}
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTravelerMutation } from "@voyantjs/bookings-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useEffect } from "react";
8
+ import { useForm } from "react-hook-form";
9
+ import { z } from "zod/v4";
10
+ const travelerFormSchema = z.object({
11
+ firstName: z.string().min(1, "First name is required"),
12
+ lastName: z.string().min(1, "Last name is required"),
13
+ email: z.string().email().optional().or(z.literal("")).nullable(),
14
+ phone: z.string().optional().nullable(),
15
+ specialRequests: z.string().optional().nullable(),
16
+ });
17
+ export function TravelerDialog({ open, onOpenChange, bookingId, traveler, onSuccess, }) {
18
+ const isEditing = Boolean(traveler);
19
+ const { create, update } = useTravelerMutation(bookingId);
20
+ const form = useForm({
21
+ resolver: zodResolver(travelerFormSchema),
22
+ defaultValues: {
23
+ firstName: "",
24
+ lastName: "",
25
+ email: "",
26
+ phone: "",
27
+ specialRequests: "",
28
+ },
29
+ });
30
+ useEffect(() => {
31
+ if (open && traveler) {
32
+ form.reset({
33
+ firstName: traveler.firstName,
34
+ lastName: traveler.lastName,
35
+ email: traveler.email ?? "",
36
+ phone: traveler.phone ?? "",
37
+ specialRequests: traveler.specialRequests ?? "",
38
+ });
39
+ }
40
+ else if (open) {
41
+ form.reset();
42
+ }
43
+ }, [form, open, traveler]);
44
+ const onSubmit = async (values) => {
45
+ const payload = {
46
+ firstName: values.firstName,
47
+ lastName: values.lastName,
48
+ email: values.email || null,
49
+ phone: values.phone || null,
50
+ specialRequests: values.specialRequests || null,
51
+ isPrimary: traveler?.isPrimary ?? false,
52
+ };
53
+ if (isEditing) {
54
+ await update.mutateAsync({ id: traveler.id, input: payload });
55
+ }
56
+ else {
57
+ await create.mutateAsync(payload);
58
+ }
59
+ onOpenChange(false);
60
+ onSuccess?.();
61
+ };
62
+ const isSubmitting = create.isPending || update.isPending;
63
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Traveler" : "Add Traveler" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col overflow-hidden", children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "First Name" }), _jsx(Input, { ...form.register("firstName"), placeholder: "John" }), form.formState.errors.firstName && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.firstName.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Last Name" }), _jsx(Input, { ...form.register("lastName"), placeholder: "Smith" }), form.formState.errors.lastName && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.lastName.message }))] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Email" }), _jsx(Input, { ...form.register("email"), type: "email", placeholder: "john@example.com" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Phone" }), _jsx(Input, { ...form.register("phone"), placeholder: "+44 7911 123456" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Special Requests" }), _jsx(Textarea, { ...form.register("specialRequests"), placeholder: "Any special requests..." })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", size: "sm", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? "Save Changes" : "Add Traveler"] })] })] })] }) }));
64
+ }
@@ -0,0 +1,5 @@
1
+ export interface TravelerListProps {
2
+ bookingId: string;
3
+ }
4
+ export declare function TravelerList({ bookingId }: TravelerListProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=traveler-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traveler-list.d.ts","sourceRoot":"","sources":["../../src/components/traveler-list.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAiG5D"}
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTravelerMutation, useTravelers, } from "@voyantjs/bookings-react";
4
+ import { Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/voyant-ui/components";
5
+ import { Pencil, Plus, Trash2, Users } from "lucide-react";
6
+ import * as React from "react";
7
+ import { TravelerDialog } from "./traveler-dialog";
8
+ export function TravelerList({ bookingId }) {
9
+ const [dialogOpen, setDialogOpen] = React.useState(false);
10
+ const [editing, setEditing] = React.useState(undefined);
11
+ const { data } = useTravelers(bookingId);
12
+ const { remove } = useTravelerMutation(bookingId);
13
+ const travelers = data?.data ?? [];
14
+ return (_jsxs(Card, { "data-slot": "traveler-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(Users, { className: "h-4 w-4" }), "Travelers"] }), _jsxs(Button, { size: "sm", onClick: () => {
15
+ setEditing(undefined);
16
+ setDialogOpen(true);
17
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Add Traveler"] })] }), _jsx(CardContent, { children: travelers.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No travelers yet." })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: "Name" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Email" }), _jsx("th", { className: "p-2 text-left font-medium", children: "Phone" }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: travelers.map((traveler) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsxs("td", { className: "p-2", children: [traveler.firstName, " ", traveler.lastName] }), _jsx("td", { className: "p-2", children: traveler.email ?? "-" }), _jsx("td", { className: "p-2", children: traveler.phone ?? "-" }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
18
+ setEditing(traveler);
19
+ setDialogOpen(true);
20
+ }, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: () => {
21
+ if (confirm("Delete this traveler?")) {
22
+ remove.mutate(traveler.id);
23
+ }
24
+ }, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }) })] }, traveler.id))) })] }) })) }), _jsx(TravelerDialog, { open: dialogOpen, onOpenChange: (nextOpen) => {
25
+ setDialogOpen(nextOpen);
26
+ if (!nextOpen) {
27
+ setEditing(undefined);
28
+ }
29
+ }, bookingId: bookingId, traveler: editing, onSuccess: () => {
30
+ setEditing(undefined);
31
+ } })] }));
32
+ }
@@ -0,0 +1,50 @@
1
+ /** Details of a successfully-validated voucher. */
2
+ export interface PickedVoucher {
3
+ id: string;
4
+ code: string;
5
+ label: string | null;
6
+ currency: string | null;
7
+ remainingAmountCents: number | null;
8
+ expiresAt: string | null;
9
+ }
10
+ export interface VoucherPickerValue {
11
+ /** Code typed by the operator. Not cleared on failure so they can correct a typo. */
12
+ code: string;
13
+ /** Populated only when the last validate call succeeded. */
14
+ picked: PickedVoucher | null;
15
+ /** Reason returned by the server when validate fails, or a client-side message. */
16
+ error: string | null;
17
+ }
18
+ export declare const emptyVoucherPickerValue: VoucherPickerValue;
19
+ export interface VoucherPickerSectionProps {
20
+ value: VoucherPickerValue;
21
+ onChange: (value: VoucherPickerValue) => void;
22
+ /**
23
+ * Context for the validate call — when provided, the server rejects vouchers
24
+ * locked to a different booking / mismatched currency / insufficient balance.
25
+ */
26
+ bookingId?: string;
27
+ currency?: string;
28
+ amountCents?: number;
29
+ labels?: {
30
+ heading?: string;
31
+ codePlaceholder?: string;
32
+ apply?: string;
33
+ clear?: string;
34
+ remainingLabel?: string;
35
+ invalidLabel?: string;
36
+ };
37
+ }
38
+ /**
39
+ * Voucher picker for booking-create flows. Operator enters a code, clicks
40
+ * Apply, and the server-side `/v1/public/vouchers/validate` runs all the
41
+ * usual guards (status, expiry, currency, booking-assignment, balance).
42
+ *
43
+ * The section only *validates* — it doesn't redeem. Redemption happens when
44
+ * the parent calls `POST /v1/finance/vouchers/:id/redeem` at submit time,
45
+ * after the booking exists and the final amount is known. Validate being
46
+ * idempotent means the operator can try a code, correct a typo, and try
47
+ * again without leaving a trail.
48
+ */
49
+ export declare function VoucherPickerSection({ value, onChange, bookingId, currency, amountCents, labels, }: VoucherPickerSectionProps): import("react/jsx-runtime").JSX.Element;
50
+ //# sourceMappingURL=voucher-picker-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voucher-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/voucher-picker-section.tsx"],"names":[],"mappings":"AAMA,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAA;IACZ,4DAA4D;IAC5D,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,eAAO,MAAM,uBAAuB,EAAE,kBAIrC,CAAA;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC7C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AA0BD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,MAAM,GACP,EAAE,yBAAyB,2CAuG3B"}
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePublicVoucherValidationMutation } from "@voyantjs/finance-react";
4
+ import { Button, Input, Label } from "@voyantjs/voyant-ui/components";
5
+ import { CheckCircle2, Loader2, XCircle } from "lucide-react";
6
+ export const emptyVoucherPickerValue = {
7
+ code: "",
8
+ picked: null,
9
+ error: null,
10
+ };
11
+ const DEFAULT_LABELS = {
12
+ heading: "Voucher (optional)",
13
+ codePlaceholder: "Enter voucher code...",
14
+ apply: "Apply",
15
+ clear: "Clear",
16
+ remainingLabel: "Remaining balance:",
17
+ invalidLabel: "This voucher can't be applied:",
18
+ };
19
+ const REASON_MESSAGES = {
20
+ not_found: "Voucher code not found.",
21
+ inactive: "Voucher is not active.",
22
+ not_started: "Voucher is not yet valid.",
23
+ expired: "Voucher has expired.",
24
+ booking_mismatch: "Voucher is assigned to a different booking.",
25
+ currency_mismatch: "Voucher currency does not match the booking.",
26
+ insufficient_balance: "Voucher balance is too low for the selected amount.",
27
+ };
28
+ function formatAmount(cents, currency) {
29
+ if (cents == null)
30
+ return "—";
31
+ return `${(cents / 100).toFixed(2)}${currency ? ` ${currency}` : ""}`;
32
+ }
33
+ /**
34
+ * Voucher picker for booking-create flows. Operator enters a code, clicks
35
+ * Apply, and the server-side `/v1/public/vouchers/validate` runs all the
36
+ * usual guards (status, expiry, currency, booking-assignment, balance).
37
+ *
38
+ * The section only *validates* — it doesn't redeem. Redemption happens when
39
+ * the parent calls `POST /v1/finance/vouchers/:id/redeem` at submit time,
40
+ * after the booking exists and the final amount is known. Validate being
41
+ * idempotent means the operator can try a code, correct a typo, and try
42
+ * again without leaving a trail.
43
+ */
44
+ export function VoucherPickerSection({ value, onChange, bookingId, currency, amountCents, labels, }) {
45
+ const merged = { ...DEFAULT_LABELS, ...labels };
46
+ const validate = usePublicVoucherValidationMutation();
47
+ const handleApply = async () => {
48
+ const code = value.code.trim();
49
+ if (!code)
50
+ return;
51
+ try {
52
+ const { data } = await validate.mutateAsync({
53
+ code,
54
+ bookingId: bookingId ?? undefined,
55
+ currency: currency ?? undefined,
56
+ amountCents: amountCents ?? undefined,
57
+ });
58
+ if (data.valid && data.voucher) {
59
+ onChange({
60
+ code,
61
+ picked: {
62
+ id: data.voucher.id,
63
+ code: data.voucher.code,
64
+ label: data.voucher.label,
65
+ currency: data.voucher.currency,
66
+ remainingAmountCents: data.voucher.remainingAmountCents,
67
+ expiresAt: data.voucher.expiresAt,
68
+ },
69
+ error: null,
70
+ });
71
+ return;
72
+ }
73
+ onChange({
74
+ code,
75
+ picked: null,
76
+ error: REASON_MESSAGES[data.reason ?? ""] ?? "Voucher is not valid.",
77
+ });
78
+ }
79
+ catch (err) {
80
+ onChange({
81
+ code,
82
+ picked: null,
83
+ error: err instanceof Error ? err.message : "Voucher lookup failed.",
84
+ });
85
+ }
86
+ };
87
+ const handleClear = () => onChange(emptyVoucherPickerValue);
88
+ return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsx(Label, { children: merged.heading }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: value.code, onChange: (e) => onChange({ ...value, code: e.target.value, error: null }), onKeyDown: (e) => {
89
+ if (e.key === "Enter") {
90
+ e.preventDefault();
91
+ void handleApply();
92
+ }
93
+ }, placeholder: merged.codePlaceholder, disabled: validate.isPending || Boolean(value.picked) }), value.picked ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleClear, children: merged.clear })) : (_jsxs(Button, { type: "button", size: "sm", onClick: () => void handleApply(), disabled: validate.isPending || !value.code.trim(), children: [validate.isPending && _jsx(Loader2, { className: "mr-1 h-3.5 w-3.5 animate-spin" }), merged.apply] }))] }), value.picked && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-600" }), _jsxs("span", { children: [merged.remainingLabel, " ", _jsx("strong", { children: formatAmount(value.picked.remainingAmountCents, value.picked.currency) })] })] })), value.error && (_jsxs("div", { className: "flex items-start gap-2 text-sm text-destructive", children: [_jsx(XCircle, { className: "mt-0.5 h-4 w-4" }), _jsxs("span", { children: [merged.invalidLabel, " ", value.error] })] }))] }));
94
+ }
@@ -0,0 +1,33 @@
1
+ export { BookingActivityTimeline, type BookingActivityTimelineProps, } from "./components/booking-activity-timeline";
2
+ export { BookingCancellationDialog, type BookingCancellationDialogProps, } from "./components/booking-cancellation-dialog";
3
+ export { BookingCreateDialog, type BookingCreateDialogProps, } from "./components/booking-create-dialog";
4
+ export { BookingDialog, type BookingDialogProps } from "./components/booking-dialog";
5
+ export { BookingDocumentDialog, type BookingDocumentDialogProps, } from "./components/booking-document-dialog";
6
+ export { BookingDocumentList, type BookingDocumentListProps, } from "./components/booking-document-list";
7
+ export { BookingGroupLinkDialog, type BookingGroupLinkDialogProps, } from "./components/booking-group-link-dialog";
8
+ export { BookingGroupSection, type BookingGroupSectionProps, } from "./components/booking-group-section";
9
+ export { BookingGuaranteeDialog, type BookingGuaranteeDialogProps, } from "./components/booking-guarantee-dialog";
10
+ export { BookingGuaranteeList, type BookingGuaranteeListProps, } from "./components/booking-guarantee-list";
11
+ export { BookingItemDialog, type BookingItemDialogProps } from "./components/booking-item-dialog";
12
+ export { BookingItemList, type BookingItemListProps } from "./components/booking-item-list";
13
+ export { BookingItemTravelers, type BookingItemTravelersProps, } from "./components/booking-item-travelers";
14
+ export { BookingList, type BookingListProps } from "./components/booking-list";
15
+ export { BookingNotes, type BookingNotesProps } from "./components/booking-notes";
16
+ export { BookingPaymentScheduleDialog, type BookingPaymentScheduleDialogProps, } from "./components/booking-payment-schedule-dialog";
17
+ export { BookingPaymentScheduleList, type BookingPaymentScheduleListProps, } from "./components/booking-payment-schedule-list";
18
+ export { BookingPaymentsSummary, type BookingPaymentsSummaryProps, } from "./components/booking-payments-summary";
19
+ export { FileDropzone, type FileDropzoneProps, type UploadedFile } from "./components/file-dropzone";
20
+ export { type PassengerEntry, type PassengerListValue, type PassengerRole, PassengersSection, type PassengersSectionProps, type RoomUnitOption, } from "./components/passengers-section";
21
+ export { type PaymentScheduleMode, PaymentScheduleSection, type PaymentScheduleSectionProps, type PaymentScheduleValue, } from "./components/payment-schedule-section";
22
+ export { type NewPersonValue, type PersonPickerMode, PersonPickerSection, type PersonPickerSectionProps, type PersonPickerValue, } from "./components/person-picker-section";
23
+ export { type PriceBreakdownLine, PriceBreakdownSection, type PriceBreakdownSectionProps, } from "./components/price-breakdown-section";
24
+ export { ProductPickerSection, type ProductPickerSectionProps, type ProductPickerValue, } from "./components/product-picker-section";
25
+ export { RoomsStepperSection, type RoomsStepperSectionProps, type RoomsStepperValue, } from "./components/rooms-stepper-section";
26
+ export { type SharedRoomMode, SharedRoomSection, type SharedRoomSectionProps, type SharedRoomValue, } from "./components/shared-room-section";
27
+ export { StatusChangeDialog, type StatusChangeDialogProps } from "./components/status-change-dialog";
28
+ export { SupplierStatusDialog, type SupplierStatusDialogProps, } from "./components/supplier-status-dialog";
29
+ export { SupplierStatusList, type SupplierStatusListProps } from "./components/supplier-status-list";
30
+ export { TravelerDialog, type TravelerDialogProps } from "./components/traveler-dialog";
31
+ export { TravelerList, type TravelerListProps } from "./components/traveler-list";
32
+ export { type PickedVoucher, VoucherPickerSection, type VoucherPickerSectionProps, type VoucherPickerValue, } from "./components/voucher-picker-section";
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ export { BookingActivityTimeline, } from "./components/booking-activity-timeline";
2
+ export { BookingCancellationDialog, } from "./components/booking-cancellation-dialog";
3
+ export { BookingCreateDialog, } from "./components/booking-create-dialog";
4
+ export { BookingDialog } from "./components/booking-dialog";
5
+ export { BookingDocumentDialog, } from "./components/booking-document-dialog";
6
+ export { BookingDocumentList, } from "./components/booking-document-list";
7
+ export { BookingGroupLinkDialog, } from "./components/booking-group-link-dialog";
8
+ export { BookingGroupSection, } from "./components/booking-group-section";
9
+ export { BookingGuaranteeDialog, } from "./components/booking-guarantee-dialog";
10
+ export { BookingGuaranteeList, } from "./components/booking-guarantee-list";
11
+ export { BookingItemDialog } from "./components/booking-item-dialog";
12
+ export { BookingItemList } from "./components/booking-item-list";
13
+ export { BookingItemTravelers, } from "./components/booking-item-travelers";
14
+ export { BookingList } from "./components/booking-list";
15
+ export { BookingNotes } from "./components/booking-notes";
16
+ export { BookingPaymentScheduleDialog, } from "./components/booking-payment-schedule-dialog";
17
+ export { BookingPaymentScheduleList, } from "./components/booking-payment-schedule-list";
18
+ export { BookingPaymentsSummary, } from "./components/booking-payments-summary";
19
+ export { FileDropzone } from "./components/file-dropzone";
20
+ export { PassengersSection, } from "./components/passengers-section";
21
+ export { PaymentScheduleSection, } from "./components/payment-schedule-section";
22
+ export { PersonPickerSection, } from "./components/person-picker-section";
23
+ export { PriceBreakdownSection, } from "./components/price-breakdown-section";
24
+ export { ProductPickerSection, } from "./components/product-picker-section";
25
+ export { RoomsStepperSection, } from "./components/rooms-stepper-section";
26
+ export { SharedRoomSection, } from "./components/shared-room-section";
27
+ export { StatusChangeDialog } from "./components/status-change-dialog";
28
+ export { SupplierStatusDialog, } from "./components/supplier-status-dialog";
29
+ export { SupplierStatusList } from "./components/supplier-status-list";
30
+ export { TravelerDialog } from "./components/traveler-dialog";
31
+ export { TravelerList } from "./components/traveler-list";
32
+ export { VoucherPickerSection, } from "./components/voucher-picker-section";
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@voyantjs/bookings-ui",
3
+ "version": "0.13.0",
4
+ "license": "FSL-1.1-Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/voyantjs/voyant.git",
8
+ "directory": "packages/bookings-ui"
9
+ },
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "exports": {
13
+ ".": "./src/index.ts",
14
+ "./components/*": "./src/components/*.tsx"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.build.json",
18
+ "clean": "rm -rf dist",
19
+ "prepack": "pnpm run build",
20
+ "typecheck": "tsc --noEmit",
21
+ "lint": "biome check src/",
22
+ "test": "vitest run --passWithNoTests"
23
+ },
24
+ "peerDependencies": {
25
+ "@tanstack/react-query": "^5.0.0",
26
+ "@voyantjs/availability-react": "workspace:*",
27
+ "@voyantjs/bookings-react": "workspace:*",
28
+ "@voyantjs/crm-react": "workspace:*",
29
+ "@voyantjs/finance-react": "workspace:*",
30
+ "@voyantjs/legal-react": "workspace:*",
31
+ "@voyantjs/products-react": "workspace:*",
32
+ "@voyantjs/voyant-ui": "workspace:*",
33
+ "react": "^19.0.0",
34
+ "react-dom": "^19.0.0",
35
+ "react-hook-form": "^7.60.0",
36
+ "zod": "^3.25.76"
37
+ },
38
+ "devDependencies": {
39
+ "@tanstack/react-query": "^5.96.2",
40
+ "@types/react": "^19.2.14",
41
+ "@types/react-dom": "^19.2.3",
42
+ "@voyantjs/availability-react": "workspace:*",
43
+ "@voyantjs/bookings-react": "workspace:*",
44
+ "@voyantjs/crm-react": "workspace:*",
45
+ "@voyantjs/finance-react": "workspace:*",
46
+ "@voyantjs/legal-react": "workspace:*",
47
+ "@voyantjs/products-react": "workspace:*",
48
+ "@voyantjs/voyant-typescript-config": "workspace:*",
49
+ "@voyantjs/voyant-ui": "workspace:*",
50
+ "lucide-react": "^0.475.0",
51
+ "react": "^19.2.4",
52
+ "react-dom": "^19.2.4",
53
+ "react-hook-form": "^7.60.0",
54
+ "typescript": "^6.0.2",
55
+ "vitest": "^4.1.2",
56
+ "zod": "^3.25.76"
57
+ },
58
+ "files": [
59
+ "dist"
60
+ ],
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "exports": {
64
+ ".": {
65
+ "types": "./dist/index.d.ts",
66
+ "import": "./dist/index.js"
67
+ },
68
+ "./components/*": {
69
+ "types": "./dist/components/*.d.ts",
70
+ "import": "./dist/components/*.js"
71
+ }
72
+ },
73
+ "main": "./dist/index.js",
74
+ "types": "./dist/index.d.ts"
75
+ }
76
+ }