@voyantjs/bookings-ui 0.16.0 → 0.18.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 (85) hide show
  1. package/LICENSE +201 -109
  2. package/README.md +11 -0
  3. package/dist/components/booking-activity-timeline.d.ts.map +1 -1
  4. package/dist/components/booking-activity-timeline.js +34 -14
  5. package/dist/components/booking-cancellation-dialog.d.ts.map +1 -1
  6. package/dist/components/booking-cancellation-dialog.js +15 -16
  7. package/dist/components/booking-create-dialog.d.ts.map +1 -1
  8. package/dist/components/booking-create-dialog.js +77 -13
  9. package/dist/components/booking-dialog.d.ts.map +1 -1
  10. package/dist/components/booking-dialog.js +27 -21
  11. package/dist/components/booking-document-dialog.d.ts.map +1 -1
  12. package/dist/components/booking-document-dialog.js +27 -13
  13. package/dist/components/booking-document-list.d.ts.map +1 -1
  14. package/dist/components/booking-document-list.js +9 -4
  15. package/dist/components/booking-group-link-dialog.d.ts.map +1 -1
  16. package/dist/components/booking-group-link-dialog.js +17 -6
  17. package/dist/components/booking-group-section.d.ts.map +1 -1
  18. package/dist/components/booking-group-section.js +8 -2
  19. package/dist/components/booking-guarantee-dialog.d.ts.map +1 -1
  20. package/dist/components/booking-guarantee-dialog.js +30 -14
  21. package/dist/components/booking-guarantee-list.d.ts.map +1 -1
  22. package/dist/components/booking-guarantee-list.js +11 -8
  23. package/dist/components/booking-item-dialog.d.ts.map +1 -1
  24. package/dist/components/booking-item-dialog.js +34 -20
  25. package/dist/components/booking-item-list.d.ts.map +1 -1
  26. package/dist/components/booking-item-list.js +10 -9
  27. package/dist/components/booking-item-travelers.d.ts.map +1 -1
  28. package/dist/components/booking-item-travelers.js +9 -4
  29. package/dist/components/booking-list.d.ts.map +1 -1
  30. package/dist/components/booking-list.js +17 -8
  31. package/dist/components/booking-notes.d.ts.map +1 -1
  32. package/dist/components/booking-notes.js +5 -2
  33. package/dist/components/booking-payment-schedule-dialog.d.ts.map +1 -1
  34. package/dist/components/booking-payment-schedule-dialog.js +31 -12
  35. package/dist/components/booking-payment-schedule-list.d.ts.map +1 -1
  36. package/dist/components/booking-payment-schedule-list.js +7 -6
  37. package/dist/components/booking-payments-summary.d.ts.map +1 -1
  38. package/dist/components/booking-payments-summary.js +7 -4
  39. package/dist/components/file-dropzone.d.ts.map +1 -1
  40. package/dist/components/file-dropzone.js +25 -15
  41. package/dist/components/passengers-section.d.ts.map +1 -1
  42. package/dist/components/passengers-section.js +3 -17
  43. package/dist/components/payment-schedule-section.d.ts.map +1 -1
  44. package/dist/components/payment-schedule-section.js +3 -14
  45. package/dist/components/person-picker-section.d.ts.map +1 -1
  46. package/dist/components/person-picker-section.js +3 -19
  47. package/dist/components/price-breakdown-section.d.ts.map +1 -1
  48. package/dist/components/price-breakdown-section.js +15 -18
  49. package/dist/components/product-picker-section.d.ts.map +1 -1
  50. package/dist/components/product-picker-section.js +3 -8
  51. package/dist/components/rooms-stepper-section.d.ts.map +1 -1
  52. package/dist/components/rooms-stepper-section.js +4 -9
  53. package/dist/components/shared-room-section.d.ts.map +1 -1
  54. package/dist/components/shared-room-section.js +3 -9
  55. package/dist/components/status-change-dialog.d.ts.map +1 -1
  56. package/dist/components/status-change-dialog.js +6 -1
  57. package/dist/components/supplier-status-dialog.d.ts.map +1 -1
  58. package/dist/components/supplier-status-dialog.js +31 -15
  59. package/dist/components/supplier-status-list.d.ts.map +1 -1
  60. package/dist/components/supplier-status-list.js +10 -2
  61. package/dist/components/traveler-dialog.d.ts.map +1 -1
  62. package/dist/components/traveler-dialog.js +17 -8
  63. package/dist/components/traveler-list.d.ts.map +1 -1
  64. package/dist/components/traveler-list.js +5 -3
  65. package/dist/components/voucher-picker-section.d.ts.map +1 -1
  66. package/dist/components/voucher-picker-section.js +11 -26
  67. package/dist/i18n/en.d.ts +797 -0
  68. package/dist/i18n/en.d.ts.map +1 -0
  69. package/dist/i18n/en.js +796 -0
  70. package/dist/i18n/index.d.ts +5 -0
  71. package/dist/i18n/index.d.ts.map +1 -0
  72. package/dist/i18n/index.js +3 -0
  73. package/dist/i18n/messages.d.ts +684 -0
  74. package/dist/i18n/messages.d.ts.map +1 -0
  75. package/dist/i18n/messages.js +1 -0
  76. package/dist/i18n/provider.d.ts +1617 -0
  77. package/dist/i18n/provider.d.ts.map +1 -0
  78. package/dist/i18n/provider.js +45 -0
  79. package/dist/i18n/ro.d.ts +797 -0
  80. package/dist/i18n/ro.d.ts.map +1 -0
  81. package/dist/i18n/ro.js +796 -0
  82. package/dist/index.d.ts +1 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +1 -0
  85. package/package.json +40 -20
@@ -8,30 +8,39 @@ import { Loader2 } from "lucide-react";
8
8
  import { useEffect } from "react";
9
9
  import { useForm } from "react-hook-form";
10
10
  import { z } from "zod/v4";
11
- const supplierStatusFormSchema = z.object({
12
- serviceName: z.string().min(1, "Service name is required"),
13
- status: z.enum(["pending", "confirmed", "rejected", "cancelled"]),
14
- supplierReference: z.string().optional().nullable(),
15
- costCurrency: z.string().min(3).max(3, "Use 3-letter ISO code"),
16
- costAmountCents: z.coerce.number().int().min(0),
17
- notes: z.string().optional().nullable(),
18
- });
11
+ import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
12
+ function createSupplierStatusFormSchema(messages) {
13
+ return z.object({
14
+ serviceName: z.string().min(1, messages.supplierStatusDialog.validation.serviceNameRequired),
15
+ status: z.enum(["pending", "confirmed", "rejected", "cancelled"]),
16
+ supplierReference: z.string().optional().nullable(),
17
+ costCurrency: z
18
+ .string()
19
+ .min(3)
20
+ .max(3, messages.supplierStatusDialog.validation.costCurrencyInvalid),
21
+ costAmountCents: z.coerce.number().int().min(0),
22
+ notes: z.string().optional().nullable(),
23
+ });
24
+ }
19
25
  const CONFIRMATION_STATUSES = [
20
- { value: "pending", label: "Pending" },
21
- { value: "confirmed", label: "Confirmed" },
22
- { value: "rejected", label: "Rejected" },
23
- { value: "cancelled", label: "Cancelled" },
26
+ { value: "pending" },
27
+ { value: "confirmed" },
28
+ { value: "rejected" },
29
+ { value: "cancelled" },
24
30
  ];
31
+ const DEFAULT_CURRENCY = "EUR"; // i18n-literal-ok ISO default currency
25
32
  export function SupplierStatusDialog({ open, onOpenChange, bookingId, supplierStatus, onSuccess, }) {
26
33
  const isEditing = Boolean(supplierStatus);
27
34
  const { create, update } = useSupplierStatusMutation(bookingId);
35
+ const messages = useBookingsUiMessagesOrDefault();
36
+ const supplierStatusFormSchema = createSupplierStatusFormSchema(messages);
28
37
  const form = useForm({
29
38
  resolver: zodResolver(supplierStatusFormSchema),
30
39
  defaultValues: {
31
40
  serviceName: "",
32
41
  status: "pending",
33
42
  supplierReference: "",
34
- costCurrency: "EUR",
43
+ costCurrency: DEFAULT_CURRENCY,
35
44
  costAmountCents: 0,
36
45
  notes: "",
37
46
  },
@@ -70,8 +79,15 @@ export function SupplierStatusDialog({ open, onOpenChange, bookingId, supplierSt
70
79
  onSuccess?.();
71
80
  };
72
81
  const isSubmitting = create.isPending || update.isPending;
73
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Update Supplier Status" : "Add Supplier Status" }) }), _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: "Service Name" }), _jsx(Input, { ...form.register("serviceName"), placeholder: "Hotel Dubrovnik Palace", disabled: isEditing }), form.formState.errors.serviceName && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.serviceName.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Status" }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), items: CONFIRMATION_STATUSES, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CONFIRMATION_STATUSES.map((status) => (_jsx(SelectItem, { value: status.value, children: status.label }, status.value))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost Currency" }), _jsx(CurrencyCombobox, { value: form.watch("costCurrency") || null, onChange: (next) => form.setValue("costCurrency", next ?? "EUR", {
82
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
83
+ ? messages.supplierStatusDialog.titles.edit
84
+ : messages.supplierStatusDialog.titles.create }) }), _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: messages.supplierStatusDialog.fields.serviceName }), _jsx(Input, { ...form.register("serviceName"), placeholder: messages.supplierStatusDialog.placeholders.serviceName, disabled: isEditing }), form.formState.errors.serviceName && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.serviceName.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.supplierStatusDialog.fields.status }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => form.setValue("status", value), items: CONFIRMATION_STATUSES.map((status) => ({
85
+ ...status,
86
+ label: messages.common.supplierStatusLabels[status.value],
87
+ })), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CONFIRMATION_STATUSES.map((status) => (_jsx(SelectItem, { value: status.value, children: messages.common.supplierStatusLabels[status.value] }, status.value))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.supplierStatusDialog.fields.costCurrency }), _jsx(CurrencyCombobox, { value: form.watch("costCurrency") || null, onChange: (next) => form.setValue("costCurrency", next ?? DEFAULT_CURRENCY, {
74
88
  shouldValidate: true,
75
89
  shouldDirty: true,
76
- }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost Amount (cents)" }), _jsx(Input, { ...form.register("costAmountCents", { valueAsNumber: true }), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Supplier Reference" }), _jsx(Input, { ...form.register("supplierReference"), placeholder: "CONF-12345" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes"), placeholder: "Additional notes..." })] })] }), _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"] })] })] })] }) }));
90
+ }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.supplierStatusDialog.fields.costAmountCents }), _jsx(Input, { ...form.register("costAmountCents", { valueAsNumber: true }), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.supplierStatusDialog.fields.supplierReference }), _jsx(Input, { ...form.register("supplierReference"), placeholder: messages.supplierStatusDialog.placeholders.supplierReference })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.supplierStatusDialog.fields.notes }), _jsx(Textarea, { ...form.register("notes"), placeholder: messages.supplierStatusDialog.placeholders.notes })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", size: "sm", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing
91
+ ? messages.common.saveChanges
92
+ : messages.supplierStatusDialog.actions.addSupplierStatus] })] })] })] }) }));
77
93
  }
@@ -1 +1 @@
1
- {"version":3,"file":"supplier-status-list.d.ts","sourceRoot":"","sources":["../../src/components/supplier-status-list.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAA;CAClB;AASD,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE,uBAAuB,2CA+FxE"}
1
+ {"version":3,"file":"supplier-status-list.d.ts","sourceRoot":"","sources":["../../src/components/supplier-status-list.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAA;CAClB;AASD,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE,uBAAuB,2CA+GxE"}
@@ -4,6 +4,7 @@ import { useSupplierStatuses } from "@voyantjs/bookings-react";
4
4
  import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
5
5
  import { Pencil, Plus } from "lucide-react";
6
6
  import * as React from "react";
7
+ import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
7
8
  import { SupplierStatusDialog } from "./supplier-status-dialog";
8
9
  const supplierStatusVariant = {
9
10
  pending: "outline",
@@ -15,11 +16,18 @@ export function SupplierStatusList({ bookingId }) {
15
16
  const [dialogOpen, setDialogOpen] = React.useState(false);
16
17
  const [editing, setEditing] = React.useState(undefined);
17
18
  const { data } = useSupplierStatuses(bookingId);
19
+ const { formatCurrency, formatDate } = useBookingsUiI18nOrDefault();
20
+ const messages = useBookingsUiMessagesOrDefault();
18
21
  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: () => {
22
+ return (_jsxs(Card, { "data-slot": "supplier-status-list", children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { children: messages.supplierStatusList.title }), _jsxs(Button, { size: "sm", onClick: () => {
20
23
  setEditing(undefined);
21
24
  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: () => {
25
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), messages.supplierStatusList.addSupplier] })] }), _jsx(CardContent, { children: statuses.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.supplierStatusList.empty })) : (_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: messages.supplierStatusList.columns.service }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.supplierStatusList.columns.status }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.supplierStatusList.columns.cost }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.supplierStatusList.columns.reference }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.supplierStatusList.columns.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", children: messages.common.supplierStatusLabels[status.status] }) }), _jsx("td", { className: "p-2 font-mono", children: status.costAmountCents == null || !status.costCurrency
26
+ ? messages.supplierStatusList.values.costUnavailable
27
+ : formatCurrency(status.costAmountCents / 100, status.costCurrency) }), _jsx("td", { className: "p-2", children: status.supplierReference ??
28
+ messages.supplierStatusList.values.referenceUnavailable }), _jsx("td", { className: "p-2", children: status.confirmedAt
29
+ ? formatDate(status.confirmedAt)
30
+ : messages.supplierStatusList.values.confirmedUnavailable }), _jsx("td", { className: "p-2", children: _jsx("button", { type: "button", onClick: () => {
23
31
  setEditing(status);
24
32
  setDialogOpen(true);
25
33
  }, 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) => {
@@ -1 +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"}
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;AAkC1F,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,2CAwIrB"}
@@ -7,16 +7,21 @@ import { Loader2 } from "lucide-react";
7
7
  import { useEffect } from "react";
8
8
  import { useForm } from "react-hook-form";
9
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
- });
10
+ import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
11
+ function createTravelerFormSchema(messages) {
12
+ return z.object({
13
+ firstName: z.string().min(1, messages.travelerDialog.validation.firstNameRequired),
14
+ lastName: z.string().min(1, messages.travelerDialog.validation.lastNameRequired),
15
+ email: z.string().email().optional().or(z.literal("")).nullable(),
16
+ phone: z.string().optional().nullable(),
17
+ specialRequests: z.string().optional().nullable(),
18
+ });
19
+ }
17
20
  export function TravelerDialog({ open, onOpenChange, bookingId, traveler, onSuccess, }) {
18
21
  const isEditing = Boolean(traveler);
19
22
  const { create, update } = useTravelerMutation(bookingId);
23
+ const messages = useBookingsUiMessagesOrDefault();
24
+ const travelerFormSchema = createTravelerFormSchema(messages);
20
25
  const form = useForm({
21
26
  resolver: zodResolver(travelerFormSchema),
22
27
  defaultValues: {
@@ -60,5 +65,9 @@ export function TravelerDialog({ open, onOpenChange, bookingId, traveler, onSucc
60
65
  onSuccess?.();
61
66
  };
62
67
  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"] })] })] })] }) }));
68
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
69
+ ? messages.travelerDialog.titles.edit
70
+ : messages.travelerDialog.titles.create }) }), _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: messages.travelerDialog.fields.firstName }), _jsx(Input, { ...form.register("firstName"), placeholder: messages.travelerDialog.placeholders.firstName }), 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: messages.travelerDialog.fields.lastName }), _jsx(Input, { ...form.register("lastName"), placeholder: messages.travelerDialog.placeholders.lastName }), 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: messages.travelerDialog.fields.email }), _jsx(Input, { ...form.register("email"), type: "email", placeholder: messages.travelerDialog.placeholders.email })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.travelerDialog.fields.phone }), _jsx(Input, { ...form.register("phone"), placeholder: messages.travelerDialog.placeholders.phone })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.travelerDialog.fields.specialRequests }), _jsx(Textarea, { ...form.register("specialRequests"), placeholder: messages.travelerDialog.placeholders.specialRequests })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", size: "sm", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing
71
+ ? messages.common.saveChanges
72
+ : messages.travelerDialog.actions.addTraveler] })] })] })] }) }));
64
73
  }
@@ -1 +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"}
1
+ {"version":3,"file":"traveler-list.d.ts","sourceRoot":"","sources":["../../src/components/traveler-list.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CA8G5D"}
@@ -4,21 +4,23 @@ import { useTravelerMutation, useTravelers, } from "@voyantjs/bookings-react";
4
4
  import { Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
5
5
  import { Pencil, Plus, Trash2, Users } from "lucide-react";
6
6
  import * as React from "react";
7
+ import { useBookingsUiMessagesOrDefault } from "../i18n/provider";
7
8
  import { TravelerDialog } from "./traveler-dialog";
8
9
  export function TravelerList({ bookingId }) {
9
10
  const [dialogOpen, setDialogOpen] = React.useState(false);
10
11
  const [editing, setEditing] = React.useState(undefined);
11
12
  const { data } = useTravelers(bookingId);
12
13
  const { remove } = useTravelerMutation(bookingId);
14
+ const messages = useBookingsUiMessagesOrDefault();
13
15
  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: () => {
16
+ 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" }), messages.travelerList.title] }), _jsxs(Button, { size: "sm", onClick: () => {
15
17
  setEditing(undefined);
16
18
  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: () => {
19
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), messages.travelerList.addTraveler] })] }), _jsx(CardContent, { children: travelers.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: messages.travelerList.empty })) : (_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: messages.travelerList.columns.name }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.email }), _jsx("th", { className: "p-2 text-left font-medium", children: messages.travelerList.columns.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 ?? messages.travelerList.values.emailUnavailable }), _jsx("td", { className: "p-2", children: traveler.phone ?? messages.travelerList.values.phoneUnavailable }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => {
18
20
  setEditing(traveler);
19
21
  setDialogOpen(true);
20
22
  }, 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?")) {
23
+ if (confirm(messages.travelerList.actions.deleteConfirm)) {
22
24
  remove.mutate(traveler.id);
23
25
  }
24
26
  }, 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) => {
@@ -1 +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"}
1
+ {"version":3,"file":"voucher-picker-section.d.ts","sourceRoot":"","sources":["../../src/components/voucher-picker-section.tsx"],"names":[],"mappings":"AAOA,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;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,MAAM,GACP,EAAE,yBAAyB,2CAiH3B"}
@@ -3,33 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { usePublicVoucherValidationMutation } from "@voyantjs/finance-react";
4
4
  import { Button, Input, Label } from "@voyantjs/ui/components";
5
5
  import { CheckCircle2, Loader2, XCircle } from "lucide-react";
6
+ import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider";
6
7
  export const emptyVoucherPickerValue = {
7
8
  code: "",
8
9
  picked: null,
9
10
  error: null,
10
11
  };
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
12
  /**
34
13
  * Voucher picker for booking-create flows. Operator enters a code, clicks
35
14
  * Apply, and the server-side `/v1/public/vouchers/validate` runs all the
@@ -42,7 +21,9 @@ function formatAmount(cents, currency) {
42
21
  * again without leaving a trail.
43
22
  */
44
23
  export function VoucherPickerSection({ value, onChange, bookingId, currency, amountCents, labels, }) {
45
- const merged = { ...DEFAULT_LABELS, ...labels };
24
+ const { formatCurrency } = useBookingsUiI18nOrDefault();
25
+ const messages = useBookingsUiMessagesOrDefault();
26
+ const merged = { ...messages.voucherPickerSection.labels, ...labels };
46
27
  const validate = usePublicVoucherValidationMutation();
47
28
  const handleApply = async () => {
48
29
  const code = value.code.trim();
@@ -73,14 +54,16 @@ export function VoucherPickerSection({ value, onChange, bookingId, currency, amo
73
54
  onChange({
74
55
  code,
75
56
  picked: null,
76
- error: REASON_MESSAGES[data.reason ?? ""] ?? "Voucher is not valid.",
57
+ error: messages.voucherPickerSection.reasonMessages[data.reason] ?? messages.voucherPickerSection.validation.invalid,
77
58
  });
78
59
  }
79
60
  catch (err) {
80
61
  onChange({
81
62
  code,
82
63
  picked: null,
83
- error: err instanceof Error ? err.message : "Voucher lookup failed.",
64
+ error: err instanceof Error
65
+ ? err.message
66
+ : messages.voucherPickerSection.validation.lookupFailed,
84
67
  });
85
68
  }
86
69
  };
@@ -90,5 +73,7 @@ export function VoucherPickerSection({ value, onChange, bookingId, currency, amo
90
73
  e.preventDefault();
91
74
  void handleApply();
92
75
  }
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] })] }))] }));
76
+ }, 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: value.picked.remainingAmountCents == null || !value.picked.currency
77
+ ? messages.voucherPickerSection.validation.amountUnavailable
78
+ : formatCurrency(value.picked.remainingAmountCents / 100, 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
79
  }