@voyantjs/products-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 (64) hide show
  1. package/LICENSE +201 -109
  2. package/README.md +11 -0
  3. package/dist/components/option-unit-dialog.d.ts.map +1 -1
  4. package/dist/components/option-unit-dialog.js +7 -3
  5. package/dist/components/option-unit-form.d.ts.map +1 -1
  6. package/dist/components/option-unit-form.js +16 -9
  7. package/dist/components/product-category-combobox.d.ts.map +1 -1
  8. package/dist/components/product-category-combobox.js +6 -4
  9. package/dist/components/product-category-dialog.d.ts.map +1 -1
  10. package/dist/components/product-category-dialog.js +7 -3
  11. package/dist/components/product-category-form.d.ts.map +1 -1
  12. package/dist/components/product-category-form.js +8 -4
  13. package/dist/components/product-category-list.d.ts.map +1 -1
  14. package/dist/components/product-category-list.js +11 -5
  15. package/dist/components/product-day-dialog.d.ts.map +1 -1
  16. package/dist/components/product-day-dialog.js +7 -3
  17. package/dist/components/product-day-form.d.ts.map +1 -1
  18. package/dist/components/product-day-form.js +9 -3
  19. package/dist/components/product-itinerary-dialog.d.ts.map +1 -1
  20. package/dist/components/product-itinerary-dialog.js +13 -5
  21. package/dist/components/product-media-dialog.d.ts.map +1 -1
  22. package/dist/components/product-media-dialog.js +7 -3
  23. package/dist/components/product-media-form.d.ts.map +1 -1
  24. package/dist/components/product-media-form.js +16 -7
  25. package/dist/components/product-media-section.d.ts.map +1 -1
  26. package/dist/components/product-media-section.js +19 -11
  27. package/dist/components/product-option-dialog.d.ts.map +1 -1
  28. package/dist/components/product-option-dialog.js +7 -3
  29. package/dist/components/product-option-form.d.ts.map +1 -1
  30. package/dist/components/product-option-form.js +11 -8
  31. package/dist/components/product-options-section.d.ts.map +1 -1
  32. package/dist/components/product-options-section.js +17 -13
  33. package/dist/components/product-tag-dialog.d.ts.map +1 -1
  34. package/dist/components/product-tag-dialog.js +7 -3
  35. package/dist/components/product-tag-form.d.ts.map +1 -1
  36. package/dist/components/product-tag-form.js +7 -3
  37. package/dist/components/product-tag-list.d.ts.map +1 -1
  38. package/dist/components/product-tag-list.js +9 -5
  39. package/dist/components/product-type-combobox.d.ts +1 -1
  40. package/dist/components/product-type-combobox.d.ts.map +1 -1
  41. package/dist/components/product-type-combobox.js +6 -2
  42. package/dist/components/product-version-dialog.d.ts.map +1 -1
  43. package/dist/components/product-version-dialog.js +5 -3
  44. package/dist/components/product-versions-section.d.ts.map +1 -1
  45. package/dist/components/product-versions-section.js +7 -2
  46. package/dist/i18n/en.d.ts +402 -0
  47. package/dist/i18n/en.d.ts.map +1 -0
  48. package/dist/i18n/en.js +401 -0
  49. package/dist/i18n/index.d.ts +5 -0
  50. package/dist/i18n/index.d.ts.map +1 -0
  51. package/dist/i18n/index.js +3 -0
  52. package/dist/i18n/messages.d.ts +402 -0
  53. package/dist/i18n/messages.d.ts.map +1 -0
  54. package/dist/i18n/messages.js +1 -0
  55. package/dist/i18n/provider.d.ts +826 -0
  56. package/dist/i18n/provider.d.ts.map +1 -0
  57. package/dist/i18n/provider.js +44 -0
  58. package/dist/i18n/ro.d.ts +402 -0
  59. package/dist/i18n/ro.d.ts.map +1 -0
  60. package/dist/i18n/ro.js +401 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +1 -0
  64. package/package.json +35 -14
@@ -8,6 +8,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@voy
8
8
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
9
9
  import { ChevronDown, ChevronRight, Copy, Loader2, Pencil, Plus, Trash2 } from "lucide-react";
10
10
  import * as React from "react";
11
+ import { useProductsUiMessagesOrDefault } from "../i18n/provider";
11
12
  import { OptionUnitDialog } from "./option-unit-dialog";
12
13
  import { ProductOptionDialog } from "./product-option-dialog";
13
14
  const optionStatusVariant = {
@@ -21,7 +22,8 @@ function formatRange(min, max) {
21
22
  }
22
23
  return `${min ?? 0}–${max ?? "∞"}`;
23
24
  }
24
- export function ProductOptionsSection({ productId, pageSize = 100, title = "Options and units", description = "Manage option variants and the units available under each option.", renderOptionDetails, }) {
25
+ export function ProductOptionsSection({ productId, pageSize = 100, title, description, renderOptionDetails, }) {
26
+ const messages = useProductsUiMessagesOrDefault();
25
27
  const [expandedOptionId, setExpandedOptionId] = React.useState(null);
26
28
  const [dialogOpen, setDialogOpen] = React.useState(false);
27
29
  const [editingOption, setEditingOption] = React.useState(undefined);
@@ -34,10 +36,12 @@ export function ProductOptionsSection({ productId, pageSize = 100, title = "Opti
34
36
  const duplicatePricing = useDuplicateOptionPricingMutation();
35
37
  const options = React.useMemo(() => (data?.data ?? []).slice().sort((a, b) => a.sortOrder - b.sortOrder), [data?.data]);
36
38
  const nextSortOrder = options.length > 0 ? Math.max(...options.map((option) => option.sortOrder)) + 1 : 0;
37
- return (_jsxs(Card, { "data-slot": "product-options-section", children: [_jsxs(CardHeader, { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(CardTitle, { children: title }), _jsx(CardDescription, { children: description })] }), _jsxs(Button, { onClick: () => {
39
+ const resolvedTitle = title ?? messages.productOptionsSection.titles.default;
40
+ const resolvedDescription = description ?? messages.productOptionsSection.descriptions.default;
41
+ return (_jsxs(Card, { "data-slot": "product-options-section", children: [_jsxs(CardHeader, { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(CardTitle, { children: resolvedTitle }), _jsx(CardDescription, { children: resolvedDescription })] }), _jsxs(Button, { onClick: () => {
38
42
  setEditingOption(undefined);
39
43
  setDialogOpen(true);
40
- }, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), "Add option"] })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [isPending ? (_jsx("div", { className: "flex min-h-24 items-center justify-center", children: _jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" }) })) : isError ? (_jsx("p", { className: "text-sm text-destructive", children: "Failed to load product options." })) : options.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No options configured for this product." })) : (options.map((option) => (_jsx(OptionRow, { option: option, expanded: expandedOptionId === option.id, onToggle: () => setExpandedOptionId((current) => (current === option.id ? null : option.id)), onEdit: () => {
44
+ }, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.productOptionsSection.actions.addOption] })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [isPending ? (_jsx("div", { className: "flex min-h-24 items-center justify-center", children: _jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" }) })) : isError ? (_jsx("p", { className: "text-sm text-destructive", children: messages.productOptionsSection.loadingError.options })) : options.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: messages.productOptionsSection.empty.options })) : (options.map((option) => (_jsx(OptionRow, { option: option, expanded: expandedOptionId === option.id, onToggle: () => setExpandedOptionId((current) => (current === option.id ? null : option.id)), onEdit: () => {
41
45
  setEditingOption(option);
42
46
  setDialogOpen(true);
43
47
  }, onDuplicate: () => {
@@ -52,35 +56,35 @@ export function ProductOptionsSection({ productId, pageSize = 100, title = "Opti
52
56
  },
53
57
  });
54
58
  }, onDelete: () => {
55
- if (confirm(`Delete option "${option.name}" and all its units?`)) {
59
+ if (confirm(messages.productOptionsSection.deleteConfirm.option.replace("{name}", option.name))) {
56
60
  remove.mutate(option.id);
57
61
  }
58
- }, children: renderOptionDetails?.(option) }, option.id)))), _jsx(ProductOptionDialog, { open: dialogOpen, onOpenChange: setDialogOpen, productId: productId, option: editingOption, sortOrder: nextSortOrder, onSuccess: () => {
62
+ }, messages: messages, children: renderOptionDetails?.(option) }, option.id)))), _jsx(ProductOptionDialog, { open: dialogOpen, onOpenChange: setDialogOpen, productId: productId, option: editingOption, sortOrder: nextSortOrder, onSuccess: () => {
59
63
  setDialogOpen(false);
60
64
  setEditingOption(undefined);
61
65
  } })] })] }));
62
66
  }
63
- function OptionRow({ option, expanded, onToggle, onEdit, onDuplicate, onDelete, children, }) {
64
- return (_jsxs("div", { className: "rounded-md border", children: [_jsxs("div", { className: "flex items-center gap-3 p-3", children: [_jsx("button", { type: "button", onClick: onToggle, className: "text-muted-foreground transition-colors hover:text-foreground", children: expanded ? _jsx(ChevronDown, { className: "size-4" }) : _jsx(ChevronRight, { className: "size-4" }) }), _jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: option.name }), option.code ? (_jsx("span", { className: "font-mono text-xs text-muted-foreground", children: option.code })) : null, _jsx(Badge, { variant: optionStatusVariant[option.status] ?? "outline", className: "capitalize", children: option.status }), option.isDefault ? _jsx(Badge, { variant: "secondary", children: "Default" }) : null] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onDuplicate, children: _jsx(Copy, { className: "size-4", "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onEdit, children: _jsx(Pencil, { className: "size-4", "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onDelete, children: _jsx(Trash2, { className: "size-4", "aria-hidden": "true" }) })] })] }), expanded ? (_jsxs("div", { className: "flex flex-col gap-4 border-t bg-muted/30 p-3", children: [_jsx(UnitsPanel, { optionId: option.id }), children] })) : null] }));
67
+ function OptionRow({ option, expanded, onToggle, onEdit, onDuplicate, onDelete, messages, children, }) {
68
+ return (_jsxs("div", { className: "rounded-md border", children: [_jsxs("div", { className: "flex items-center gap-3 p-3", children: [_jsx("button", { type: "button", onClick: onToggle, className: "text-muted-foreground transition-colors hover:text-foreground", children: expanded ? _jsx(ChevronDown, { className: "size-4" }) : _jsx(ChevronRight, { className: "size-4" }) }), _jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: option.name }), option.code ? (_jsx("span", { className: "font-mono text-xs text-muted-foreground", children: option.code })) : null, _jsx(Badge, { variant: optionStatusVariant[option.status] ?? "outline", children: messages.common.optionStatusLabels[option.status] }), option.isDefault ? (_jsx(Badge, { variant: "secondary", children: messages.productOptionsSection.badges.defaultOption })) : null] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onDuplicate, "aria-label": messages.productOptionsSection.actions.duplicate, children: _jsx(Copy, { className: "size-4", "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onEdit, "aria-label": messages.productOptionsSection.actions.edit, children: _jsx(Pencil, { className: "size-4", "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onDelete, "aria-label": messages.productOptionsSection.actions.delete, children: _jsx(Trash2, { className: "size-4", "aria-hidden": "true" }) })] })] }), expanded ? (_jsxs("div", { className: "flex flex-col gap-4 border-t bg-muted/30 p-3", children: [_jsx(UnitsPanel, { optionId: option.id, messages: messages }), children] })) : null] }));
65
69
  }
66
- function UnitsPanel({ optionId }) {
70
+ function UnitsPanel({ optionId, messages, }) {
67
71
  const [dialogOpen, setDialogOpen] = React.useState(false);
68
72
  const [editingUnit, setEditingUnit] = React.useState(undefined);
69
73
  const { data, isPending, isError } = useOptionUnits({ optionId, limit: 100 });
70
74
  const { remove } = useOptionUnitMutation();
71
75
  const units = React.useMemo(() => (data?.data ?? []).slice().sort((a, b) => a.sortOrder - b.sortOrder), [data?.data]);
72
76
  const nextSortOrder = units.length > 0 ? Math.max(...units.map((unit) => unit.sortOrder)) + 1 : 0;
73
- return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Units" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Configure the selectable units that belong to this option." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
77
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: messages.productOptionsSection.titles.units }), _jsx("p", { className: "text-xs text-muted-foreground", children: messages.productOptionsSection.descriptions.units })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
74
78
  setEditingUnit(undefined);
75
79
  setDialogOpen(true);
76
- }, children: [_jsx(Plus, { className: "mr-2 size-3.5", "aria-hidden": "true" }), "Add unit"] })] }), isPending ? (_jsx("div", { className: "flex min-h-20 items-center justify-center rounded-md border bg-background", children: _jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" }) })) : isError ? (_jsx("p", { className: "text-sm text-destructive", children: "Failed to load option units." })) : units.length === 0 ? (_jsx("p", { className: "rounded-md border bg-background px-3 py-4 text-sm text-muted-foreground", children: "No units configured for this option." })) : (_jsx("div", { className: "rounded-md border bg-background", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Type" }), _jsx(TableHead, { children: "Name" }), _jsx(TableHead, { children: "Quantity" }), _jsx(TableHead, { children: "Age" }), _jsx(TableHead, { children: "Occupancy" }), _jsx(TableHead, { className: "w-[88px] text-right", children: "Actions" })] }) }), _jsx(TableBody, { children: units.map((unit) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Badge, { variant: "outline", className: "capitalize", children: unit.unitType }) }), _jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: unit.name }), unit.code ? (_jsx("div", { className: "font-mono text-xs text-muted-foreground", children: unit.code })) : null] }), _jsx(TableCell, { className: "font-mono text-xs", children: formatRange(unit.minQuantity, unit.maxQuantity) }), _jsx(TableCell, { className: "font-mono text-xs", children: formatRange(unit.minAge, unit.maxAge) }), _jsx(TableCell, { className: "font-mono text-xs", children: formatRange(unit.occupancyMin, unit.occupancyMax) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => {
80
+ }, children: [_jsx(Plus, { className: "mr-2 size-3.5", "aria-hidden": "true" }), messages.productOptionsSection.actions.addUnit] })] }), isPending ? (_jsx("div", { className: "flex min-h-20 items-center justify-center rounded-md border bg-background", children: _jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" }) })) : isError ? (_jsx("p", { className: "text-sm text-destructive", children: messages.productOptionsSection.loadingError.units })) : units.length === 0 ? (_jsx("p", { className: "rounded-md border bg-background px-3 py-4 text-sm text-muted-foreground", children: messages.productOptionsSection.empty.units })) : (_jsx("div", { className: "rounded-md border bg-background", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: messages.productOptionsSection.columns.unitType }), _jsx(TableHead, { children: messages.productOptionsSection.columns.unitName }), _jsx(TableHead, { children: messages.productOptionsSection.columns.quantity }), _jsx(TableHead, { children: messages.productOptionsSection.columns.age }), _jsx(TableHead, { children: messages.productOptionsSection.columns.occupancy }), _jsx(TableHead, { className: "w-[88px] text-right", children: messages.productOptionsSection.columns.actions })] }) }), _jsx(TableBody, { children: units.map((unit) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Badge, { variant: "outline", children: messages.common.optionUnitTypeLabels[unit.unitType] }) }), _jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: unit.name }), unit.code ? (_jsx("div", { className: "font-mono text-xs text-muted-foreground", children: unit.code })) : null] }), _jsx(TableCell, { className: "font-mono text-xs", children: formatRange(unit.minQuantity, unit.maxQuantity) }), _jsx(TableCell, { className: "font-mono text-xs", children: formatRange(unit.minAge, unit.maxAge) }), _jsx(TableCell, { className: "font-mono text-xs", children: formatRange(unit.occupancyMin, unit.occupancyMax) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => {
77
81
  setEditingUnit(unit);
78
82
  setDialogOpen(true);
79
- }, children: _jsx(Pencil, { className: "size-4", "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => {
80
- if (confirm(`Delete unit "${unit.name}"?`)) {
83
+ }, "aria-label": messages.productOptionsSection.actions.edit, children: _jsx(Pencil, { className: "size-4", "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => {
84
+ if (confirm(messages.productOptionsSection.deleteConfirm.unit.replace("{name}", unit.name))) {
81
85
  remove.mutate(unit.id);
82
86
  }
83
- }, children: _jsx(Trash2, { className: "size-4", "aria-hidden": "true" }) })] }) })] }, unit.id))) })] }) })), _jsx(OptionUnitDialog, { open: dialogOpen, onOpenChange: setDialogOpen, optionId: optionId, unit: editingUnit, sortOrder: nextSortOrder, onSuccess: () => {
87
+ }, "aria-label": messages.productOptionsSection.actions.delete, children: _jsx(Trash2, { className: "size-4", "aria-hidden": "true" }) })] }) })] }, unit.id))) })] }) })), _jsx(OptionUnitDialog, { open: dialogOpen, onOpenChange: setDialogOpen, optionId: optionId, unit: editingUnit, sortOrder: nextSortOrder, onSuccess: () => {
84
88
  setDialogOpen(false);
85
89
  setEditingUnit(undefined);
86
90
  } })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"product-tag-dialog.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAYhE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IACtB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAC5C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CAyB7F"}
1
+ {"version":3,"file":"product-tag-dialog.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAahE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,GAAG,CAAC,EAAE,gBAAgB,CAAA;IACtB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAC5C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,qBAAqB,2CA8B7F"}
@@ -1,12 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@voyantjs/ui/components/dialog";
4
+ import { useProductsUiMessagesOrDefault } from "../i18n/provider";
4
5
  import { ProductTagForm } from "./product-tag-form";
5
6
  export function ProductTagDialog({ open, onOpenChange, tag, onSuccess }) {
6
7
  const isEdit = Boolean(tag);
7
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "data-slot": "product-tag-dialog", className: "sm:max-w-[480px]", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: isEdit ? "Edit product tag" : "New product tag" }), _jsx(DialogDescription, { children: isEdit
8
- ? "Update the tag used to label and filter products."
9
- : "Create a reusable tag for filtering and classification." })] }), _jsx(ProductTagForm, { mode: tag ? { kind: "edit", tag } : { kind: "create" }, onSuccess: (saved) => {
8
+ const messages = useProductsUiMessagesOrDefault();
9
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "data-slot": "product-tag-dialog", className: "sm:max-w-[480px]", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: isEdit
10
+ ? messages.productTagDialog.titles.edit
11
+ : messages.productTagDialog.titles.create }), _jsx(DialogDescription, { children: isEdit
12
+ ? messages.productTagDialog.descriptions.edit
13
+ : messages.productTagDialog.descriptions.create })] }), _jsx(ProductTagForm, { mode: tag ? { kind: "edit", tag } : { kind: "create" }, onSuccess: (saved) => {
10
14
  onSuccess?.(saved);
11
15
  onOpenChange(false);
12
16
  }, onCancel: () => onOpenChange(false) })] }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"product-tag-form.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-form.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,0BAA0B,CAAA;AAOjC,KAAK,IAAI,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAExE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAYD,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,mBAAmB,2CA+DhF"}
1
+ {"version":3,"file":"product-tag-form.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-form.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,0BAA0B,CAAA;AASjC,KAAK,IAAI,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAExE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAA;IACV,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAYD,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,mBAAmB,2CAkEhF"}
@@ -6,6 +6,7 @@ import { Input } from "@voyantjs/ui/components/input";
6
6
  import { Label } from "@voyantjs/ui/components/label";
7
7
  import { Loader2 } from "lucide-react";
8
8
  import * as React from "react";
9
+ import { useProductsUiMessagesOrDefault } from "../i18n/provider";
9
10
  function initialState(mode) {
10
11
  return {
11
12
  name: mode.kind === "edit" ? mode.tag.name : "",
@@ -18,6 +19,7 @@ export function ProductTagForm({ mode, onSuccess, onCancel }) {
18
19
  const [state, setState] = React.useState(() => initialState(mode));
19
20
  const [error, setError] = React.useState(null);
20
21
  const { create, update } = useProductTagMutation();
22
+ const messages = useProductsUiMessagesOrDefault();
21
23
  React.useEffect(() => {
22
24
  setState(initialState(mode));
23
25
  setError(null);
@@ -27,7 +29,7 @@ export function ProductTagForm({ mode, onSuccess, onCancel }) {
27
29
  event.preventDefault();
28
30
  setError(null);
29
31
  if (!state.name.trim()) {
30
- setError("Tag name is required.");
32
+ setError(messages.productTagForm.validation.nameRequired);
31
33
  return;
32
34
  }
33
35
  try {
@@ -37,8 +39,10 @@ export function ProductTagForm({ mode, onSuccess, onCancel }) {
37
39
  onSuccess?.(tag);
38
40
  }
39
41
  catch (err) {
40
- setError(err instanceof Error ? err.message : "Failed to save product tag.");
42
+ setError(err instanceof Error ? err.message : messages.productTagForm.validation.saveFailed);
41
43
  }
42
44
  };
43
- return (_jsxs("form", { "data-slot": "product-tag-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "product-tag-name", children: "Name" }), _jsx(Input, { id: "product-tag-name", required: true, autoFocus: true, value: state.name, onChange: (event) => setState({ name: event.target.value }), placeholder: "Family Friendly" })] }), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex items-center justify-end gap-2", children: [onCancel ? (_jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, children: "Cancel" })) : null, _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? (_jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": "true" })) : null, mode.kind === "edit" ? "Save changes" : "Create tag"] })] })] }));
45
+ return (_jsxs("form", { "data-slot": "product-tag-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "product-tag-name", children: messages.productTagForm.fields.name }), _jsx(Input, { id: "product-tag-name", required: true, autoFocus: true, value: state.name, onChange: (event) => setState({ name: event.target.value }), placeholder: messages.productTagForm.placeholders.name })] }), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex items-center justify-end gap-2", children: [onCancel ? (_jsx(Button, { type: "button", variant: "ghost", onClick: onCancel, children: messages.common.cancel })) : null, _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? (_jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": "true" })) : null, mode.kind === "edit"
46
+ ? messages.common.saveChanges
47
+ : messages.productTagForm.actions.createTag] })] })] }));
44
48
  }
@@ -1 +1 @@
1
- {"version":3,"file":"product-tag-list.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-list.tsx"],"names":[],"mappings":"AA6BA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAc,EAAE,GAAE,mBAAwB,2CA6I1E"}
1
+ {"version":3,"file":"product-tag-list.d.ts","sourceRoot":"","sources":["../../src/components/product-tag-list.tsx"],"names":[],"mappings":"AA8BA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAc,EAAE,GAAE,mBAAwB,2CAkJ1E"}
@@ -7,6 +7,7 @@ import { Input } from "@voyantjs/ui/components/input";
7
7
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
8
8
  import { Loader2, MoreHorizontal, Pencil, Plus, Search, Trash2 } from "lucide-react";
9
9
  import * as React from "react";
10
+ import { useProductsUiMessagesOrDefault } from "../i18n/provider";
10
11
  import { ProductTagDialog } from "./product-tag-dialog";
11
12
  export function ProductTagList({ pageSize = 200 } = {}) {
12
13
  const [search, setSearch] = React.useState("");
@@ -19,22 +20,25 @@ export function ProductTagList({ pageSize = 200 } = {}) {
19
20
  offset,
20
21
  });
21
22
  const { remove } = useProductTagMutation();
23
+ const messages = useProductsUiMessagesOrDefault();
22
24
  const tags = data?.data ?? [];
23
25
  const total = data?.total ?? 0;
24
26
  const page = Math.floor(offset / pageSize) + 1;
25
27
  const pageCount = Math.max(1, Math.ceil(total / pageSize));
26
- return (_jsxs("div", { "data-slot": "product-tag-list", className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "relative w-full max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Search product tags\u2026", value: search, onChange: (event) => {
28
+ return (_jsxs("div", { "data-slot": "product-tag-list", className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "relative w-full max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: messages.productTagList.searchPlaceholder, value: search, onChange: (event) => {
27
29
  setSearch(event.target.value);
28
30
  setOffset(0);
29
31
  }, className: "pl-9" })] }), _jsxs(Button, { onClick: () => {
30
32
  setEditing(undefined);
31
33
  setDialogOpen(true);
32
- }, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), "Add tag"] })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Name" }), _jsx(TableHead, { className: "w-[80px] text-right", children: "Actions" })] }) }), _jsx(TableBody, { children: isPending ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center", children: _jsx(Loader2, { className: "mx-auto size-4 animate-spin text-muted-foreground" }) }) })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center text-sm text-destructive", children: "Failed to load product tags." }) })) : tags.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center text-sm text-muted-foreground", children: "No product tags found." }) })) : (tags.map((tag) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: tag.name }), _jsx(TableCell, { className: "text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-hidden hover:bg-accent hover:text-accent-foreground", children: _jsx(MoreHorizontal, { className: "size-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => {
34
+ }, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.productTagList.addTag] })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: messages.productTagList.columns.name }), _jsx(TableHead, { className: "w-[80px] text-right", children: messages.productTagList.columns.actions })] }) }), _jsx(TableBody, { children: isPending ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center", children: _jsx(Loader2, { className: "mx-auto size-4 animate-spin text-muted-foreground" }) }) })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center text-sm text-destructive", children: messages.productTagList.loadingError }) })) : tags.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 2, className: "h-24 text-center text-sm text-muted-foreground", children: messages.productTagList.empty }) })) : (tags.map((tag) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: tag.name }), _jsx(TableCell, { className: "text-right", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-hidden hover:bg-accent hover:text-accent-foreground", children: _jsx(MoreHorizontal, { className: "size-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onClick: () => {
33
35
  setEditing(tag);
34
36
  setDialogOpen(true);
35
- }, children: [_jsx(Pencil, { className: "size-4" }), "Edit"] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", onClick: () => {
36
- if (confirm("Delete this product tag?")) {
37
+ }, children: [_jsx(Pencil, { className: "size-4" }), messages.productTagList.edit] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { variant: "destructive", onClick: () => {
38
+ if (confirm(messages.productTagList.deleteConfirm)) {
37
39
  remove.mutate(tag.id);
38
40
  }
39
- }, children: [_jsx(Trash2, { className: "size-4" }), "Delete"] })] })] }) })] }, tag.id)))) })] }) }), _jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [_jsxs("span", { children: ["Showing ", tags.length, " of ", total] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: offset === 0, onClick: () => setOffset((prev) => Math.max(0, prev - pageSize)), children: "Previous" }), _jsxs("span", { children: ["Page ", page, " / ", pageCount] }), _jsx(Button, { variant: "outline", size: "sm", disabled: offset + pageSize >= total, onClick: () => setOffset((prev) => prev + pageSize), children: "Next" })] })] }), _jsx(ProductTagDialog, { open: dialogOpen, onOpenChange: setDialogOpen, tag: editing })] }));
41
+ }, children: [_jsx(Trash2, { className: "size-4" }), messages.productTagList.delete] })] })] }) })] }, tag.id)))) })] }) }), _jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [_jsx("span", { children: messages.productTagList.showingSummary
42
+ .replace("{count}", String(tags.length))
43
+ .replace("{total}", String(total)) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: offset === 0, onClick: () => setOffset((prev) => Math.max(0, prev - pageSize)), children: messages.common.previous }), _jsxs("span", { children: [messages.common.page, " ", page, " / ", pageCount] }), _jsx(Button, { variant: "outline", size: "sm", disabled: offset + pageSize >= total, onClick: () => setOffset((prev) => prev + pageSize), children: messages.common.next })] })] }), _jsx(ProductTagDialog, { open: dialogOpen, onOpenChange: setDialogOpen, tag: editing })] }));
40
44
  }
@@ -4,6 +4,6 @@ type Props = {
4
4
  placeholder?: string;
5
5
  disabled?: boolean;
6
6
  };
7
- export declare function ProductTypeCombobox({ value, onChange, placeholder, disabled, }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export declare function ProductTypeCombobox({ value, onChange, placeholder, disabled }: Props): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
9
9
  //# sourceMappingURL=product-type-combobox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"product-type-combobox.d.ts","sourceRoot":"","sources":["../../src/components/product-type-combobox.tsx"],"names":[],"mappings":"AAYA,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAID,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,WAAqC,EACrC,QAAQ,GACT,EAAE,KAAK,2CAoEP"}
1
+ {"version":3,"file":"product-type-combobox.d.ts","sourceRoot":"","sources":["../../src/components/product-type-combobox.tsx"],"names":[],"mappings":"AAcA,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAID,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,KAAK,2CA0EpF"}
@@ -2,8 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useProductType, useProductTypes } from "@voyantjs/products-react";
3
3
  import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyantjs/ui/components/combobox";
4
4
  import * as React from "react";
5
+ import { useProductsUiMessagesOrDefault } from "../i18n/provider";
5
6
  const PAGE_SIZE = 25;
6
- export function ProductTypeCombobox({ value, onChange, placeholder = "Search product types…", disabled, }) {
7
+ export function ProductTypeCombobox({ value, onChange, placeholder, disabled }) {
8
+ const messages = useProductsUiMessagesOrDefault();
7
9
  const [search, setSearch] = React.useState("");
8
10
  const listQuery = useProductTypes({ active: true, search: search || undefined, limit: PAGE_SIZE });
9
11
  const selectedQuery = useProductType(value, { enabled: !!value });
@@ -35,7 +37,9 @@ export function ProductTypeCombobox({ value, onChange, placeholder = "Search pro
35
37
  onChange(id);
36
38
  const item = id ? itemMap.get(id) : null;
37
39
  setInputValue(item ? `${item.name} · ${item.code}` : "");
38
- }, children: [_jsx(ComboboxInput, { placeholder: placeholder, showClear: !!value }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: listQuery.isPending || selectedQuery.isPending ? "Loading…" : "No product types found." }), _jsx(ComboboxList, { children: _jsx(ComboboxCollection, { children: (id) => {
40
+ }, children: [_jsx(ComboboxInput, { placeholder: placeholder ?? messages.comboboxes.productType.placeholder, showClear: !!value }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: listQuery.isPending || selectedQuery.isPending
41
+ ? messages.common.loading
42
+ : messages.comboboxes.productType.empty }), _jsx(ComboboxList, { children: _jsx(ComboboxCollection, { children: (id) => {
39
43
  const item = itemMap.get(id);
40
44
  if (!item)
41
45
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"product-version-dialog.d.ts","sourceRoot":"","sources":["../../src/components/product-version-dialog.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GACV,EAAE,yBAAyB,2CAqE3B"}
1
+ {"version":3,"file":"product-version-dialog.d.ts","sourceRoot":"","sources":["../../src/components/product-version-dialog.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,SAAS,GACV,EAAE,yBAAyB,2CAqE3B"}
@@ -7,17 +7,19 @@ import { Label } from "@voyantjs/ui/components/label";
7
7
  import { Textarea } from "@voyantjs/ui/components/textarea";
8
8
  import { Loader2 } from "lucide-react";
9
9
  import * as React from "react";
10
+ import { useProductsUiMessagesOrDefault } from "../i18n/provider";
10
11
  export function ProductVersionDialog({ open, onOpenChange, productId, onSuccess, }) {
11
12
  const [notes, setNotes] = React.useState("");
12
13
  const [error, setError] = React.useState(null);
13
14
  const { create } = useProductVersionMutation();
15
+ const messages = useProductsUiMessagesOrDefault();
14
16
  React.useEffect(() => {
15
17
  if (open) {
16
18
  setNotes("");
17
19
  setError(null);
18
20
  }
19
21
  }, [open]);
20
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "data-slot": "product-version-dialog", className: "sm:max-w-[560px]", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Create version snapshot" }), _jsx(DialogDescription, { children: "Save the current product state, including itinerary and option structure, as a new version." })] }), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: async (event) => {
22
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "data-slot": "product-version-dialog", className: "sm:max-w-[560px]", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: messages.productVersionDialog.title }), _jsx(DialogDescription, { children: messages.productVersionDialog.description })] }), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: async (event) => {
21
23
  event.preventDefault();
22
24
  setError(null);
23
25
  try {
@@ -31,7 +33,7 @@ export function ProductVersionDialog({ open, onOpenChange, productId, onSuccess,
31
33
  catch (submissionError) {
32
34
  setError(submissionError instanceof Error
33
35
  ? submissionError.message
34
- : "Failed to create version snapshot.");
36
+ : messages.productVersionDialog.validation.saveFailed);
35
37
  }
36
- }, children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "product-version-notes", children: "Notes" }), _jsx(Textarea, { id: "product-version-notes", value: notes, onChange: (event) => setNotes(event.target.value), placeholder: "What changed in this version?" })] }), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: create.isPending, children: [create.isPending ? (_jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": "true" })) : null, "Create version"] })] })] })] }) }));
38
+ }, children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "product-version-notes", children: messages.productVersionDialog.fields.notes }), _jsx(Textarea, { id: "product-version-notes", value: notes, onChange: (event) => setNotes(event.target.value), placeholder: messages.productVersionDialog.placeholders.notes })] }), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", disabled: create.isPending, children: [create.isPending ? (_jsx(Loader2, { className: "mr-2 size-4 animate-spin", "aria-hidden": "true" })) : null, messages.productVersionDialog.actions.createVersion] })] })] })] }) }));
37
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"product-versions-section.d.ts","sourceRoot":"","sources":["../../src/components/product-versions-section.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,KAAkB,EAClB,WAA8D,GAC/D,EAAE,2BAA2B,2CAoD7B"}
1
+ {"version":3,"file":"product-versions-section.d.ts","sourceRoot":"","sources":["../../src/components/product-versions-section.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,KAAK,EACL,WAAW,GACZ,EAAE,2BAA2B,2CA0D7B"}
@@ -5,10 +5,15 @@ import { Button } from "@voyantjs/ui/components/button";
5
5
  import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@voyantjs/ui/components/card";
6
6
  import { FileText, Loader2, Plus } from "lucide-react";
7
7
  import * as React from "react";
8
+ import { useProductsUiI18nOrDefault, useProductsUiMessagesOrDefault } from "../i18n/provider";
8
9
  import { ProductVersionDialog } from "./product-version-dialog";
9
- export function ProductVersionsSection({ productId, title = "Versions", description = "Create and browse immutable product snapshots.", }) {
10
+ export function ProductVersionsSection({ productId, title, description, }) {
10
11
  const [dialogOpen, setDialogOpen] = React.useState(false);
11
12
  const { data, isPending, isError } = useProductVersions(productId);
12
13
  const versions = data?.data ?? [];
13
- return (_jsxs(Card, { "data-slot": "product-versions-section", children: [_jsxs(CardHeader, { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(CardTitle, { children: title }), _jsx(CardDescription, { children: description })] }), _jsxs(Button, { variant: "outline", onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), "Create version"] })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [isPending ? (_jsx("div", { className: "flex min-h-24 items-center justify-center", children: _jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" }) })) : isError ? (_jsx("p", { className: "text-sm text-destructive", children: "Failed to load product versions." })) : versions.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No version snapshots created yet." })) : (versions.map((version) => (_jsxs("div", { className: "flex items-center gap-4 rounded-md border p-3", children: [_jsx(FileText, { className: "size-4 text-muted-foreground", "aria-hidden": "true" }), _jsxs("div", { className: "flex-1", children: [_jsxs("p", { className: "text-sm font-medium", children: ["Version ", version.versionNumber] }), version.notes ? (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: version.notes })) : null] }), _jsxs("div", { className: "text-right text-xs text-muted-foreground", children: [_jsx("div", { children: new Date(version.createdAt).toLocaleString() }), _jsx("div", { children: version.authorId })] })] }, version.id)))), _jsx(ProductVersionDialog, { open: dialogOpen, onOpenChange: setDialogOpen, productId: productId })] })] }));
14
+ const messages = useProductsUiMessagesOrDefault();
15
+ const { formatDateTime } = useProductsUiI18nOrDefault();
16
+ const resolvedTitle = title ?? messages.productVersionsSection.titles.default;
17
+ const resolvedDescription = description ?? messages.productVersionsSection.descriptions.default;
18
+ return (_jsxs(Card, { "data-slot": "product-versions-section", children: [_jsxs(CardHeader, { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(CardTitle, { children: resolvedTitle }), _jsx(CardDescription, { children: resolvedDescription })] }), _jsxs(Button, { variant: "outline", onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.productVersionsSection.actions.createVersion] })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [isPending ? (_jsx("div", { className: "flex min-h-24 items-center justify-center", children: _jsx(Loader2, { className: "size-4 animate-spin text-muted-foreground" }) })) : isError ? (_jsx("p", { className: "text-sm text-destructive", children: messages.productVersionsSection.loadingError })) : versions.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: messages.productVersionsSection.empty })) : (versions.map((version) => (_jsxs("div", { className: "flex items-center gap-4 rounded-md border p-3", children: [_jsx(FileText, { className: "size-4 text-muted-foreground", "aria-hidden": "true" }), _jsxs("div", { className: "flex-1", children: [_jsxs("p", { className: "text-sm font-medium", children: [messages.productVersionsSection.versionLabel, " ", version.versionNumber] }), version.notes ? (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: version.notes })) : null] }), _jsxs("div", { className: "text-right text-xs text-muted-foreground", children: [_jsx("div", { children: formatDateTime(version.createdAt) }), _jsx("div", { children: version.authorId })] })] }, version.id)))), _jsx(ProductVersionDialog, { open: dialogOpen, onOpenChange: setDialogOpen, productId: productId })] })] }));
14
19
  }