@voyant-travel/legal-react 0.119.2

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 (238) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +66 -0
  3. package/dist/admin/contract-detail-host.d.ts +21 -0
  4. package/dist/admin/contract-detail-host.d.ts.map +1 -0
  5. package/dist/admin/contract-detail-host.js +118 -0
  6. package/dist/admin/contract-dialog-fields.d.ts +79 -0
  7. package/dist/admin/contract-dialog-fields.d.ts.map +1 -0
  8. package/dist/admin/contract-dialog-fields.js +178 -0
  9. package/dist/admin/contract-dialog.d.ts +4 -0
  10. package/dist/admin/contract-dialog.d.ts.map +1 -0
  11. package/dist/admin/contract-dialog.js +479 -0
  12. package/dist/admin/contract-upload-field.d.ts +12 -0
  13. package/dist/admin/contract-upload-field.d.ts.map +1 -0
  14. package/dist/admin/contract-upload-field.js +31 -0
  15. package/dist/admin/contracts-host.d.ts +10 -0
  16. package/dist/admin/contracts-host.d.ts.map +1 -0
  17. package/dist/admin/contracts-host.js +32 -0
  18. package/dist/admin/index.d.ts +78 -0
  19. package/dist/admin/index.d.ts.map +1 -0
  20. package/dist/admin/index.js +193 -0
  21. package/dist/admin/legal-admin-shared.d.ts +31 -0
  22. package/dist/admin/legal-admin-shared.d.ts.map +1 -0
  23. package/dist/admin/legal-admin-shared.js +48 -0
  24. package/dist/admin/number-series-dialog.d.ts +11 -0
  25. package/dist/admin/number-series-dialog.d.ts.map +1 -0
  26. package/dist/admin/number-series-dialog.js +110 -0
  27. package/dist/admin/number-series-host.d.ts +7 -0
  28. package/dist/admin/number-series-host.d.ts.map +1 -0
  29. package/dist/admin/number-series-host.js +12 -0
  30. package/dist/admin/pages/contract-detail-page.d.ts +7 -0
  31. package/dist/admin/pages/contract-detail-page.d.ts.map +1 -0
  32. package/dist/admin/pages/contract-detail-page.js +9 -0
  33. package/dist/admin/pages/policy-detail-page.d.ts +7 -0
  34. package/dist/admin/pages/policy-detail-page.d.ts.map +1 -0
  35. package/dist/admin/pages/policy-detail-page.js +9 -0
  36. package/dist/admin/pages/template-detail-page.d.ts +7 -0
  37. package/dist/admin/pages/template-detail-page.d.ts.map +1 -0
  38. package/dist/admin/pages/template-detail-page.js +9 -0
  39. package/dist/admin/policies-host.d.ts +9 -0
  40. package/dist/admin/policies-host.d.ts.map +1 -0
  41. package/dist/admin/policies-host.js +16 -0
  42. package/dist/admin/policy-assignment-dialog.d.ts +11 -0
  43. package/dist/admin/policy-assignment-dialog.d.ts.map +1 -0
  44. package/dist/admin/policy-assignment-dialog.js +234 -0
  45. package/dist/admin/policy-detail-host.d.ts +12 -0
  46. package/dist/admin/policy-detail-host.d.ts.map +1 -0
  47. package/dist/admin/policy-detail-host.js +17 -0
  48. package/dist/admin/policy-dialog.d.ts +10 -0
  49. package/dist/admin/policy-dialog.d.ts.map +1 -0
  50. package/dist/admin/policy-dialog.js +81 -0
  51. package/dist/admin/template-detail-host.d.ts +11 -0
  52. package/dist/admin/template-detail-host.d.ts.map +1 -0
  53. package/dist/admin/template-detail-host.js +20 -0
  54. package/dist/admin/template-dialog.d.ts +10 -0
  55. package/dist/admin/template-dialog.d.ts.map +1 -0
  56. package/dist/admin/template-dialog.js +117 -0
  57. package/dist/admin/template-version-dialog.d.ts +9 -0
  58. package/dist/admin/template-version-dialog.d.ts.map +1 -0
  59. package/dist/admin/template-version-dialog.js +67 -0
  60. package/dist/admin/templates-host.d.ts +9 -0
  61. package/dist/admin/templates-host.d.ts.map +1 -0
  62. package/dist/admin/templates-host.js +20 -0
  63. package/dist/client.d.ts +14 -0
  64. package/dist/client.d.ts.map +1 -0
  65. package/dist/client.js +69 -0
  66. package/dist/components/attachment-dialog.d.ts +11 -0
  67. package/dist/components/attachment-dialog.d.ts.map +1 -0
  68. package/dist/components/attachment-dialog.js +154 -0
  69. package/dist/components/booking-contract-card.d.ts +37 -0
  70. package/dist/components/booking-contract-card.d.ts.map +1 -0
  71. package/dist/components/booking-contract-card.js +101 -0
  72. package/dist/components/contract-detail-page.d.ts +36 -0
  73. package/dist/components/contract-detail-page.d.ts.map +1 -0
  74. package/dist/components/contract-detail-page.js +156 -0
  75. package/dist/components/contract-dialog-fields.d.ts +114 -0
  76. package/dist/components/contract-dialog-fields.d.ts.map +1 -0
  77. package/dist/components/contract-dialog-fields.js +233 -0
  78. package/dist/components/contract-dialog.d.ts +5 -0
  79. package/dist/components/contract-dialog.d.ts.map +1 -0
  80. package/dist/components/contract-dialog.js +345 -0
  81. package/dist/components/contract-send-dialog.d.ts +37 -0
  82. package/dist/components/contract-send-dialog.d.ts.map +1 -0
  83. package/dist/components/contract-send-dialog.js +56 -0
  84. package/dist/components/contracts-page.d.ts +30 -0
  85. package/dist/components/contracts-page.d.ts.map +1 -0
  86. package/dist/components/contracts-page.js +192 -0
  87. package/dist/components/number-series-page.d.ts +13 -0
  88. package/dist/components/number-series-page.d.ts.map +1 -0
  89. package/dist/components/number-series-page.js +37 -0
  90. package/dist/components/policies-page.d.ts +13 -0
  91. package/dist/components/policies-page.d.ts.map +1 -0
  92. package/dist/components/policies-page.js +70 -0
  93. package/dist/components/policy-detail-page.d.ts +23 -0
  94. package/dist/components/policy-detail-page.d.ts.map +1 -0
  95. package/dist/components/policy-detail-page.js +171 -0
  96. package/dist/components/policy-rule-dialog.d.ts +12 -0
  97. package/dist/components/policy-rule-dialog.d.ts.map +1 -0
  98. package/dist/components/policy-rule-dialog.js +101 -0
  99. package/dist/components/policy-version-dialog.d.ts +11 -0
  100. package/dist/components/policy-version-dialog.d.ts.map +1 -0
  101. package/dist/components/policy-version-dialog.js +62 -0
  102. package/dist/components/signature-dialog.d.ts +9 -0
  103. package/dist/components/signature-dialog.d.ts.map +1 -0
  104. package/dist/components/signature-dialog.js +64 -0
  105. package/dist/components/template-detail-page.d.ts +10 -0
  106. package/dist/components/template-detail-page.d.ts.map +1 -0
  107. package/dist/components/template-detail-page.js +84 -0
  108. package/dist/components/templates-page.d.ts +27 -0
  109. package/dist/components/templates-page.d.ts.map +1 -0
  110. package/dist/components/templates-page.js +65 -0
  111. package/dist/hooks/index.d.ts +31 -0
  112. package/dist/hooks/index.d.ts.map +1 -0
  113. package/dist/hooks/index.js +30 -0
  114. package/dist/hooks/use-contract-attachment-mutation.d.ts +119 -0
  115. package/dist/hooks/use-contract-attachment-mutation.d.ts.map +1 -0
  116. package/dist/hooks/use-contract-attachment-mutation.js +72 -0
  117. package/dist/hooks/use-contract-attachments.d.ts +23 -0
  118. package/dist/hooks/use-contract-attachments.d.ts.map +1 -0
  119. package/dist/hooks/use-contract-attachments.js +12 -0
  120. package/dist/hooks/use-contract-mutation.d.ts +427 -0
  121. package/dist/hooks/use-contract-mutation.d.ts.map +1 -0
  122. package/dist/hooks/use-contract-mutation.js +151 -0
  123. package/dist/hooks/use-contract-signature-mutation.d.ts +35 -0
  124. package/dist/hooks/use-contract-signature-mutation.d.ts.map +1 -0
  125. package/dist/hooks/use-contract-signature-mutation.js +23 -0
  126. package/dist/hooks/use-contract-signatures.d.ts +28 -0
  127. package/dist/hooks/use-contract-signatures.d.ts.map +1 -0
  128. package/dist/hooks/use-contract-signatures.js +12 -0
  129. package/dist/hooks/use-contract-template-authoring.d.ts +5 -0
  130. package/dist/hooks/use-contract-template-authoring.d.ts.map +1 -0
  131. package/dist/hooks/use-contract-template-authoring.js +7 -0
  132. package/dist/hooks/use-contract-template-mutation.d.ts +56 -0
  133. package/dist/hooks/use-contract-template-mutation.d.ts.map +1 -0
  134. package/dist/hooks/use-contract-template-mutation.js +39 -0
  135. package/dist/hooks/use-contract-template-version-mutation.d.ts +22 -0
  136. package/dist/hooks/use-contract-template-version-mutation.d.ts.map +1 -0
  137. package/dist/hooks/use-contract-template-version-mutation.js +22 -0
  138. package/dist/hooks/use-contract-template-versions.d.ts +15 -0
  139. package/dist/hooks/use-contract-template-versions.d.ts.map +1 -0
  140. package/dist/hooks/use-contract-template-versions.js +12 -0
  141. package/dist/hooks/use-contract-template.d.ts +20 -0
  142. package/dist/hooks/use-contract-template.d.ts.map +1 -0
  143. package/dist/hooks/use-contract-template.js +12 -0
  144. package/dist/hooks/use-contract-templates.d.ts +26 -0
  145. package/dist/hooks/use-contract-templates.d.ts.map +1 -0
  146. package/dist/hooks/use-contract-templates.js +12 -0
  147. package/dist/hooks/use-contract.d.ts +47 -0
  148. package/dist/hooks/use-contract.d.ts.map +1 -0
  149. package/dist/hooks/use-contract.js +12 -0
  150. package/dist/hooks/use-contracts.d.ts +53 -0
  151. package/dist/hooks/use-contracts.d.ts.map +1 -0
  152. package/dist/hooks/use-contracts.js +12 -0
  153. package/dist/hooks/use-default-contract-template.d.ts +21 -0
  154. package/dist/hooks/use-default-contract-template.d.ts.map +1 -0
  155. package/dist/hooks/use-default-contract-template.js +12 -0
  156. package/dist/hooks/use-evaluate-cancellation.d.ts +46 -0
  157. package/dist/hooks/use-evaluate-cancellation.d.ts.map +1 -0
  158. package/dist/hooks/use-evaluate-cancellation.js +43 -0
  159. package/dist/hooks/use-number-series-mutation.d.ts +58 -0
  160. package/dist/hooks/use-number-series-mutation.d.ts.map +1 -0
  161. package/dist/hooks/use-number-series-mutation.js +38 -0
  162. package/dist/hooks/use-number-series.d.ts +24 -0
  163. package/dist/hooks/use-number-series.d.ts.map +1 -0
  164. package/dist/hooks/use-number-series.js +12 -0
  165. package/dist/hooks/use-policies.d.ts +22 -0
  166. package/dist/hooks/use-policies.d.ts.map +1 -0
  167. package/dist/hooks/use-policies.js +12 -0
  168. package/dist/hooks/use-policy-acceptances.d.ts +26 -0
  169. package/dist/hooks/use-policy-acceptances.d.ts.map +1 -0
  170. package/dist/hooks/use-policy-acceptances.js +12 -0
  171. package/dist/hooks/use-policy-assignment-mutation.d.ts +63 -0
  172. package/dist/hooks/use-policy-assignment-mutation.d.ts.map +1 -0
  173. package/dist/hooks/use-policy-assignment-mutation.js +41 -0
  174. package/dist/hooks/use-policy-assignments.d.ts +23 -0
  175. package/dist/hooks/use-policy-assignments.d.ts.map +1 -0
  176. package/dist/hooks/use-policy-assignments.js +12 -0
  177. package/dist/hooks/use-policy-mutation.d.ts +44 -0
  178. package/dist/hooks/use-policy-mutation.d.ts.map +1 -0
  179. package/dist/hooks/use-policy-mutation.js +40 -0
  180. package/dist/hooks/use-policy-rule-mutation.d.ts +56 -0
  181. package/dist/hooks/use-policy-rule-mutation.d.ts.map +1 -0
  182. package/dist/hooks/use-policy-rule-mutation.js +39 -0
  183. package/dist/hooks/use-policy-rules.d.ts +22 -0
  184. package/dist/hooks/use-policy-rules.d.ts.map +1 -0
  185. package/dist/hooks/use-policy-rules.js +12 -0
  186. package/dist/hooks/use-policy-version-mutation.d.ts +69 -0
  187. package/dist/hooks/use-policy-version-mutation.d.ts.map +1 -0
  188. package/dist/hooks/use-policy-version-mutation.js +50 -0
  189. package/dist/hooks/use-policy-versions.d.ts +19 -0
  190. package/dist/hooks/use-policy-versions.d.ts.map +1 -0
  191. package/dist/hooks/use-policy-versions.js +12 -0
  192. package/dist/hooks/use-policy.d.ts +16 -0
  193. package/dist/hooks/use-policy.d.ts.map +1 -0
  194. package/dist/hooks/use-policy.js +12 -0
  195. package/dist/hooks/use-resolve-policy.d.ts +72 -0
  196. package/dist/hooks/use-resolve-policy.d.ts.map +1 -0
  197. package/dist/hooks/use-resolve-policy.js +16 -0
  198. package/dist/hooks/use-term.d.ts +27 -0
  199. package/dist/hooks/use-term.d.ts.map +1 -0
  200. package/dist/hooks/use-term.js +12 -0
  201. package/dist/hooks/use-terms.d.ts +33 -0
  202. package/dist/hooks/use-terms.d.ts.map +1 -0
  203. package/dist/hooks/use-terms.js +12 -0
  204. package/dist/i18n/en.d.ts +548 -0
  205. package/dist/i18n/en.d.ts.map +1 -0
  206. package/dist/i18n/en.js +547 -0
  207. package/dist/i18n/index.d.ts +6 -0
  208. package/dist/i18n/index.d.ts.map +1 -0
  209. package/dist/i18n/index.js +4 -0
  210. package/dist/i18n/messages.d.ts +504 -0
  211. package/dist/i18n/messages.d.ts.map +1 -0
  212. package/dist/i18n/messages.js +29 -0
  213. package/dist/i18n/provider.d.ts +1118 -0
  214. package/dist/i18n/provider.d.ts.map +1 -0
  215. package/dist/i18n/provider.js +44 -0
  216. package/dist/i18n/ro.d.ts +548 -0
  217. package/dist/i18n/ro.d.ts.map +1 -0
  218. package/dist/i18n/ro.js +547 -0
  219. package/dist/index.d.ts +7 -0
  220. package/dist/index.d.ts.map +1 -0
  221. package/dist/index.js +6 -0
  222. package/dist/provider.d.ts +2 -0
  223. package/dist/provider.d.ts.map +1 -0
  224. package/dist/provider.js +1 -0
  225. package/dist/query-keys.d.ts +120 -0
  226. package/dist/query-keys.d.ts.map +1 -0
  227. package/dist/query-keys.js +27 -0
  228. package/dist/query-options.d.ts +1929 -0
  229. package/dist/query-options.d.ts.map +1 -0
  230. package/dist/query-options.js +183 -0
  231. package/dist/schemas.d.ts +1547 -0
  232. package/dist/schemas.d.ts.map +1 -0
  233. package/dist/schemas.js +269 -0
  234. package/dist/ui.d.ts +16 -0
  235. package/dist/ui.d.ts.map +1 -0
  236. package/dist/ui.js +15 -0
  237. package/package.json +159 -0
  238. package/src/styles.css +11 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-assignment-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/policy-assignment-dialog.tsx"],"names":[],"mappings":"AA6BA,OAAO,EAAE,KAAK,2BAA2B,EAAoC,MAAM,aAAa,CAAA;AA4BhG,MAAM,MAAM,cAAc,GAAG,2BAA2B,CAAA;AAExD,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE,2BAA2B,2CAiN7B"}
@@ -0,0 +1,234 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // agent-quality: file-size exception -- owner: legal-react; existing UI surface stays co-located until a dedicated split preserves behavior and tests.
3
+ import { useOperatorAdminMessages } from "@voyant-travel/admin";
4
+ import { useMarket, useMarkets } from "@voyant-travel/commerce-react/markets";
5
+ import { useChannel, useChannels } from "@voyant-travel/distribution-react";
6
+ import { useSupplier, useSuppliers } from "@voyant-travel/distribution-react/suppliers";
7
+ import { useProduct, useProducts } from "@voyant-travel/inventory-react";
8
+ import { useOrganization, useOrganizations } from "@voyant-travel/relationships-react";
9
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyant-travel/ui/components";
10
+ import { DatePicker } from "@voyant-travel/ui/components/date-picker";
11
+ import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
12
+ import { Loader2 } from "lucide-react";
13
+ import { useEffect, useMemo, useState } from "react";
14
+ import { useForm } from "react-hook-form";
15
+ import { z } from "zod/v4";
16
+ import { useLegalPolicyAssignmentMutation } from "../index.js";
17
+ import { mergeUniqueOptions, SearchableSelect } from "./legal-admin-shared.js";
18
+ // Parity with the previous operator-local entity combobox, which showed a
19
+ // hardcoded "No results." empty state; the loading label is localized via
20
+ // the shared legal contract-dialog messages.
21
+ const PICKER_EMPTY_LABEL = "No results.";
22
+ const SCOPE_VALUES = ["product", "channel", "supplier", "market", "organization", "global"];
23
+ const assignmentFormSchema = z.object({
24
+ policyId: z.string().min(1, "policyIdRequired"),
25
+ scope: z.enum(SCOPE_VALUES),
26
+ productId: z.string().optional(),
27
+ channelId: z.string().optional(),
28
+ supplierId: z.string().optional(),
29
+ marketId: z.string().optional(),
30
+ organizationId: z.string().optional(),
31
+ validFrom: z.string().optional(),
32
+ validTo: z.string().optional(),
33
+ priority: z.coerce.number().int().optional(),
34
+ });
35
+ export function PolicyAssignmentDialog({ open, onOpenChange, policyId, assignment, onSuccess, }) {
36
+ const isEditing = !!assignment;
37
+ const t = useOperatorAdminMessages().legal.policyAssignmentDialog;
38
+ const { create, update } = useLegalPolicyAssignmentMutation();
39
+ const form = useForm({
40
+ resolver: zodResolver(assignmentFormSchema),
41
+ defaultValues: {
42
+ policyId,
43
+ scope: "global",
44
+ productId: "",
45
+ channelId: "",
46
+ supplierId: "",
47
+ marketId: "",
48
+ organizationId: "",
49
+ validFrom: "",
50
+ validTo: "",
51
+ priority: 0,
52
+ },
53
+ });
54
+ useEffect(() => {
55
+ if (open && assignment) {
56
+ form.reset({
57
+ policyId: assignment.policyId,
58
+ scope: assignment.scope,
59
+ productId: assignment.productId ?? "",
60
+ channelId: assignment.channelId ?? "",
61
+ supplierId: assignment.supplierId ?? "",
62
+ marketId: assignment.marketId ?? "",
63
+ organizationId: assignment.organizationId ?? "",
64
+ validFrom: assignment.validFrom ?? "",
65
+ validTo: assignment.validTo ?? "",
66
+ priority: assignment.priority,
67
+ });
68
+ }
69
+ else if (open) {
70
+ form.reset({ policyId, scope: "global", priority: 0 });
71
+ }
72
+ }, [open, assignment, policyId, form]);
73
+ const onSubmit = async (values) => {
74
+ const payload = {
75
+ policyId: values.policyId,
76
+ scope: values.scope,
77
+ productId: values.productId || undefined,
78
+ channelId: values.channelId || undefined,
79
+ supplierId: values.supplierId || undefined,
80
+ marketId: values.marketId || undefined,
81
+ organizationId: values.organizationId || undefined,
82
+ validFrom: values.validFrom || undefined,
83
+ validTo: values.validTo || undefined,
84
+ priority: values.priority ?? 0,
85
+ };
86
+ if (isEditing && assignment) {
87
+ await update.mutateAsync({ id: assignment.id, input: payload });
88
+ }
89
+ else {
90
+ await create.mutateAsync(payload);
91
+ }
92
+ onSuccess();
93
+ };
94
+ const watchedScope = form.watch("scope");
95
+ const setReferenceField = (field, value) => {
96
+ form.setValue(field, value ?? "", { shouldDirty: true, shouldValidate: true });
97
+ };
98
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.titleEdit : t.titleNew }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), 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: t.scopeLabel }), _jsxs(Select, { items: SCOPE_VALUES.map((value) => ({ value, label: t.scopeOptions[value] })), value: form.watch("scope"), onValueChange: (v) => {
99
+ form.setValue("scope", v, {
100
+ shouldDirty: true,
101
+ shouldValidate: true,
102
+ });
103
+ form.setValue("productId", "");
104
+ form.setValue("channelId", "");
105
+ form.setValue("supplierId", "");
106
+ form.setValue("marketId", "");
107
+ form.setValue("organizationId", "");
108
+ }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SCOPE_VALUES.map((value) => (_jsx(SelectItem, { value: value, children: t.scopeOptions[value] }, value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.priorityLabel }), _jsx(Input, { ...form.register("priority"), type: "number" })] })] }), watchedScope === "product" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.productLabel }), _jsx(ProductPicker, { value: form.watch("productId") || null, onChange: (id) => setReferenceField("productId", id), placeholder: t.productSearchPlaceholder })] })), watchedScope === "channel" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.channelLabel }), _jsx(ChannelPicker, { value: form.watch("channelId") || null, onChange: (id) => setReferenceField("channelId", id), placeholder: t.channelSearchPlaceholder })] })), watchedScope === "supplier" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.supplierLabel }), _jsx(SupplierPicker, { value: form.watch("supplierId") || null, onChange: (id) => setReferenceField("supplierId", id), placeholder: t.supplierSearchPlaceholder })] })), watchedScope === "market" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.marketLabel }), _jsx(MarketPicker, { value: form.watch("marketId") || null, onChange: (id) => setReferenceField("marketId", id), placeholder: t.marketSearchPlaceholder })] })), watchedScope === "organization" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.organizationLabel }), _jsx(OrganizationPicker, { value: form.watch("organizationId") || null, onChange: (id) => setReferenceField("organizationId", id), placeholder: t.organizationSearchPlaceholder })] })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.validFromLabel }), _jsx(DatePicker, { value: form.watch("validFrom") || null, onChange: (next) => form.setValue("validFrom", next ?? "", {
109
+ shouldValidate: true,
110
+ shouldDirty: true,
111
+ }), placeholder: t.validFromPlaceholder, className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.validToLabel }), _jsx(DatePicker, { value: form.watch("validTo") || null, onChange: (next) => form.setValue("validTo", next ?? "", {
112
+ shouldValidate: true,
113
+ shouldDirty: true,
114
+ }), placeholder: t.validToPlaceholder, className: "w-full" })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.cancel }), _jsxs(Button, { type: "submit", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? t.saveChanges : t.createAction] })] })] })] }) }));
115
+ }
116
+ function usePickerLoadingLabel() {
117
+ return useOperatorAdminMessages().legal.contractDialog.loading;
118
+ }
119
+ function ProductPicker({ value, onChange, placeholder }) {
120
+ const loadingLabel = usePickerLoadingLabel();
121
+ const [search, setSearch] = useState("");
122
+ const listQuery = useProducts({ search: search || undefined, limit: 25 });
123
+ const selectedQuery = useProduct(value ?? undefined, { enabled: Boolean(value) });
124
+ const options = useMemo(() => {
125
+ const describe = (product) => [product.status, product.bookingMode].filter(Boolean).join(" / ") || undefined;
126
+ return mergeUniqueOptions(listQuery.data?.data.map((product) => ({
127
+ value: product.id,
128
+ label: product.name,
129
+ description: describe(product),
130
+ })), selectedQuery.data
131
+ ? [
132
+ {
133
+ value: selectedQuery.data.id,
134
+ label: selectedQuery.data.name,
135
+ description: describe(selectedQuery.data),
136
+ },
137
+ ]
138
+ : undefined);
139
+ }, [listQuery.data?.data, selectedQuery.data]);
140
+ return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
141
+ }
142
+ function ChannelPicker({ value, onChange, placeholder }) {
143
+ const loadingLabel = usePickerLoadingLabel();
144
+ // Channels expose no server-side search filter; load a wide page and let
145
+ // the combobox narrow client-side (same approach as the contract dialog).
146
+ const listQuery = useChannels({ limit: 250 });
147
+ const selectedQuery = useChannel(value, { enabled: Boolean(value) });
148
+ const options = useMemo(() => {
149
+ const describe = (channel) => [channel.kind, channel.status].filter(Boolean).join(" / ") || undefined;
150
+ return mergeUniqueOptions(listQuery.data?.data.map((channel) => ({
151
+ value: channel.id,
152
+ label: channel.name,
153
+ description: describe(channel),
154
+ })), selectedQuery.data
155
+ ? [
156
+ {
157
+ value: selectedQuery.data.id,
158
+ label: selectedQuery.data.name,
159
+ description: describe(selectedQuery.data),
160
+ },
161
+ ]
162
+ : undefined);
163
+ }, [listQuery.data?.data, selectedQuery.data]);
164
+ return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending) }));
165
+ }
166
+ function SupplierPicker({ value, onChange, placeholder }) {
167
+ const loadingLabel = usePickerLoadingLabel();
168
+ const [search, setSearch] = useState("");
169
+ const listQuery = useSuppliers({ search: search || undefined, limit: 25 });
170
+ const selectedQuery = useSupplier(value ?? "", { enabled: Boolean(value) });
171
+ const options = useMemo(() => {
172
+ const describe = (supplier) => [supplier.city, supplier.country].filter(Boolean).join(" / ") || undefined;
173
+ return mergeUniqueOptions(listQuery.data?.data.map((supplier) => ({
174
+ value: supplier.id,
175
+ label: supplier.name,
176
+ description: describe(supplier),
177
+ })), selectedQuery.data
178
+ ? [
179
+ {
180
+ value: selectedQuery.data.data.id,
181
+ label: selectedQuery.data.data.name,
182
+ description: describe(selectedQuery.data.data),
183
+ },
184
+ ]
185
+ : undefined);
186
+ }, [listQuery.data?.data, selectedQuery.data]);
187
+ return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
188
+ }
189
+ function MarketPicker({ value, onChange, placeholder }) {
190
+ const loadingLabel = usePickerLoadingLabel();
191
+ const [search, setSearch] = useState("");
192
+ const listQuery = useMarkets({ search: search || undefined, limit: 25 });
193
+ const selectedQuery = useMarket(value, { enabled: Boolean(value) });
194
+ const options = useMemo(() => {
195
+ const describe = (market) => [market.code, market.defaultCurrency].filter(Boolean).join(" / ") || undefined;
196
+ return mergeUniqueOptions(listQuery.data?.data.map((market) => ({
197
+ value: market.id,
198
+ label: market.name,
199
+ description: describe(market),
200
+ })), selectedQuery.data
201
+ ? [
202
+ {
203
+ value: selectedQuery.data.id,
204
+ label: selectedQuery.data.name,
205
+ description: describe(selectedQuery.data),
206
+ },
207
+ ]
208
+ : undefined);
209
+ }, [listQuery.data?.data, selectedQuery.data]);
210
+ return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
211
+ }
212
+ function OrganizationPicker({ value, onChange, placeholder }) {
213
+ const loadingLabel = usePickerLoadingLabel();
214
+ const [search, setSearch] = useState("");
215
+ const listQuery = useOrganizations({ search: search || undefined, limit: 25 });
216
+ const selectedQuery = useOrganization(value ?? undefined, { enabled: Boolean(value) });
217
+ const options = useMemo(() => {
218
+ const describe = (organization) => [organization.website, organization.industry].filter(Boolean).join(" / ") || undefined;
219
+ return mergeUniqueOptions(listQuery.data?.data.map((organization) => ({
220
+ value: organization.id,
221
+ label: organization.name,
222
+ description: describe(organization),
223
+ })), selectedQuery.data
224
+ ? [
225
+ {
226
+ value: selectedQuery.data.id,
227
+ label: selectedQuery.data.name,
228
+ description: describe(selectedQuery.data),
229
+ },
230
+ ]
231
+ : undefined);
232
+ }, [listQuery.data?.data, selectedQuery.data]);
233
+ return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
234
+ }
@@ -0,0 +1,12 @@
1
+ export interface PolicyDetailHostProps {
2
+ id: string;
3
+ }
4
+ /**
5
+ * Packaged admin host for the operator-grade policy detail page
6
+ * (packaged-admin RFC Phase 3). Back-navigation resolves through the
7
+ * `policy.list` semantic destination; the edit and assignment dialogs are
8
+ * the packaged {@link PolicyDialog} / {@link PolicyAssignmentDialog}
9
+ * (scoped pickers bound to their domain react hooks).
10
+ */
11
+ export declare function PolicyDetailHost({ id }: PolicyDetailHostProps): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=policy-detail-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/policy-detail-host.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,qBAAqB,2CAW7D"}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useAdminNavigate } from "@voyant-travel/admin";
4
+ import { PolicyDetailPage } from "../components/policy-detail-page.js";
5
+ import { PolicyAssignmentDialog } from "./policy-assignment-dialog.js";
6
+ import { PolicyDialog } from "./policy-dialog.js";
7
+ /**
8
+ * Packaged admin host for the operator-grade policy detail page
9
+ * (packaged-admin RFC Phase 3). Back-navigation resolves through the
10
+ * `policy.list` semantic destination; the edit and assignment dialogs are
11
+ * the packaged {@link PolicyDialog} / {@link PolicyAssignmentDialog}
12
+ * (scoped pickers bound to their domain react hooks).
13
+ */
14
+ export function PolicyDetailHost({ id }) {
15
+ const navigateTo = useAdminNavigate();
16
+ return (_jsx(PolicyDetailPage, { id: id, onBackToPolicies: () => navigateTo("policy.list", {}), renderPolicyDialog: (props) => _jsx(PolicyDialog, { ...props }), renderPolicyAssignmentDialog: (props) => _jsx(PolicyAssignmentDialog, { ...props }) }));
17
+ }
@@ -0,0 +1,10 @@
1
+ import { type LegalPolicyRecord } from "../index.js";
2
+ type PolicyDialogProps = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ policy?: LegalPolicyRecord;
6
+ onSuccess: () => void;
7
+ };
8
+ export declare function PolicyDialog({ open, onOpenChange, policy, onSuccess }: PolicyDialogProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=policy-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/policy-dialog.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAE,KAAK,iBAAiB,EAA0B,MAAM,aAAa,CAAA;AA4B5E,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAmIxF"}
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useOperatorAdminMessages } from "@voyant-travel/admin";
3
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyant-travel/ui/components";
4
+ import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
5
+ import { Loader2 } from "lucide-react";
6
+ import { useEffect } from "react";
7
+ import { useForm } from "react-hook-form";
8
+ import { z } from "zod/v4";
9
+ import { useLegalPolicyMutation } from "../index.js";
10
+ const KIND_VALUES = [
11
+ "cancellation",
12
+ "payment",
13
+ "terms_and_conditions",
14
+ "privacy",
15
+ "refund",
16
+ "commission",
17
+ "guarantee",
18
+ "other",
19
+ ];
20
+ const policyFormSchema = z.object({
21
+ kind: z.enum(KIND_VALUES),
22
+ name: z.string().min(1, "nameRequired"),
23
+ slug: z
24
+ .string()
25
+ .min(1, "slugRequired")
26
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "slugKebabCase"),
27
+ description: z.string().optional(),
28
+ language: z.string().min(2).max(10).optional(),
29
+ });
30
+ export function PolicyDialog({ open, onOpenChange, policy, onSuccess }) {
31
+ const isEditing = !!policy;
32
+ const t = useOperatorAdminMessages().legal.policyDialog;
33
+ const { create, update } = useLegalPolicyMutation();
34
+ const validationByCode = {
35
+ nameRequired: t.validation.nameRequired,
36
+ slugRequired: t.validation.slugRequired,
37
+ slugKebabCase: t.validation.slugKebabCase,
38
+ };
39
+ const resolveValidation = (code) => (code && validationByCode[code]) || code || "";
40
+ const form = useForm({
41
+ resolver: zodResolver(policyFormSchema),
42
+ defaultValues: {
43
+ kind: "cancellation",
44
+ name: "",
45
+ slug: "",
46
+ description: "",
47
+ language: "en",
48
+ },
49
+ });
50
+ useEffect(() => {
51
+ if (open && policy) {
52
+ form.reset({
53
+ kind: policy.kind,
54
+ name: policy.name,
55
+ slug: policy.slug,
56
+ description: policy.description ?? "",
57
+ language: policy.language,
58
+ });
59
+ }
60
+ else if (open) {
61
+ form.reset();
62
+ }
63
+ }, [open, policy, form]);
64
+ const onSubmit = async (values) => {
65
+ const payload = {
66
+ kind: values.kind,
67
+ name: values.name,
68
+ slug: values.slug,
69
+ description: values.description || undefined,
70
+ language: values.language || "en",
71
+ };
72
+ if (isEditing && policy) {
73
+ await update.mutateAsync({ id: policy.id, input: payload });
74
+ }
75
+ else {
76
+ await create.mutateAsync(payload);
77
+ }
78
+ onSuccess();
79
+ };
80
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.titleEdit : t.titleNew }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.kindLabel }), _jsxs(Select, { items: KIND_VALUES.map((value) => ({ value, label: t.kindOptions[value] })), value: form.watch("kind"), onValueChange: (v) => form.setValue("kind", v), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: KIND_VALUES.map((value) => (_jsx(SelectItem, { value: value, children: t.kindOptions[value] }, value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: t.namePlaceholder }), form.formState.errors.name && (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.name.message) }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.slugLabel }), _jsx(Input, { ...form.register("slug"), placeholder: t.slugPlaceholder }), form.formState.errors.slug && (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.slug.message) }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.descriptionLabel }), _jsx(Textarea, { ...form.register("description"), placeholder: t.descriptionPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.languageLabel }), _jsx(Input, { ...form.register("language"), placeholder: t.languagePlaceholder, maxLength: 10, className: "max-w-[120px]" })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.cancel }), _jsxs(Button, { type: "submit", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? t.saveChanges : t.createAction] })] })] })] }) }));
81
+ }
@@ -0,0 +1,11 @@
1
+ export interface TemplateDetailHostProps {
2
+ id: string;
3
+ }
4
+ /**
5
+ * Packaged admin host for the operator-grade contract template detail page
6
+ * (packaged-admin RFC Phase 3). Back-navigation resolves through the
7
+ * `contractTemplate.list` semantic destination; the edit/version dialogs
8
+ * (rich-text editor) stay lazily loaded inside the package.
9
+ */
10
+ export declare function TemplateDetailHost({ id }: TemplateDetailHostProps): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=template-detail-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/template-detail-host.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,uBAAuB,2CAmBjE"}
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useAdminNavigate } from "@voyant-travel/admin";
4
+ import { lazy, Suspense } from "react";
5
+ import { TemplateDetailPage } from "../components/template-detail-page.js";
6
+ // Lazy: both dialogs use the RichTextEditor (tiptap + prosemirror).
7
+ const TemplateDialog = lazy(() => import("./template-dialog.js").then((m) => ({ default: m.TemplateDialog })));
8
+ const TemplateVersionDialog = lazy(() => import("./template-version-dialog.js").then((m) => ({
9
+ default: m.TemplateVersionDialog,
10
+ })));
11
+ /**
12
+ * Packaged admin host for the operator-grade contract template detail page
13
+ * (packaged-admin RFC Phase 3). Back-navigation resolves through the
14
+ * `contractTemplate.list` semantic destination; the edit/version dialogs
15
+ * (rich-text editor) stay lazily loaded inside the package.
16
+ */
17
+ export function TemplateDetailHost({ id }) {
18
+ const navigateTo = useAdminNavigate();
19
+ return (_jsx(TemplateDetailPage, { id: id, onBackToTemplates: () => navigateTo("contractTemplate.list", {}), renderTemplateDialog: (props) => (_jsx(Suspense, { fallback: null, children: _jsx(TemplateDialog, { ...props }) })), renderTemplateVersionDialog: (props) => (_jsx(Suspense, { fallback: null, children: _jsx(TemplateVersionDialog, { ...props }) })) }));
20
+ }
@@ -0,0 +1,10 @@
1
+ import { type LegalContractTemplateRecord } from "../index.js";
2
+ type TemplateDialogProps = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ template?: LegalContractTemplateRecord;
6
+ onSuccess: () => void;
7
+ };
8
+ export declare function TemplateDialog({ open, onOpenChange, template, onSuccess }: TemplateDialogProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=template-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/template-dialog.tsx"],"names":[],"mappings":"AAgCA,OAAO,EACL,KAAK,2BAA2B,EAGjC,MAAM,aAAa,CAAA;AAqBpB,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,2BAA2B,CAAA;IACtC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAcD,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CAqN9F"}
@@ -0,0 +1,117 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useOperatorAdminMessages } from "@voyant-travel/admin";
3
+ import { Button, ContractTemplateAuthoringHelp, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyant-travel/ui/components";
4
+ import { RichTextEditor } from "@voyant-travel/ui/components/rich-text-editor";
5
+ import { insertPlainText, insertVariableToken, } from "@voyant-travel/ui/components/rich-text-variable-extension";
6
+ import { Switch } from "@voyant-travel/ui/components/switch";
7
+ import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
8
+ import { languages } from "@voyant-travel/utils";
9
+ import { Loader2 } from "lucide-react";
10
+ import { useEffect, useMemo, useState } from "react";
11
+ import { useForm } from "react-hook-form";
12
+ import { z } from "zod/v4";
13
+ import { useLegalContractTemplateAuthoring, useLegalContractTemplateMutation, } from "../index.js";
14
+ const SCOPE_VALUES = ["customer", "supplier", "partner", "channel", "other"];
15
+ const templateFormSchema = z.object({
16
+ name: z.string().min(1, "nameRequired"),
17
+ slug: z
18
+ .string()
19
+ .min(1, "slugRequired")
20
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "slugKebabCase"),
21
+ scope: z.enum(SCOPE_VALUES),
22
+ language: z.string().min(2).max(10).optional(),
23
+ description: z.string().optional(),
24
+ body: z.string().min(1, "bodyRequired"),
25
+ active: z.boolean(),
26
+ });
27
+ /**
28
+ * Language picker options derived from the canonical ISO 639-1 list
29
+ * shipped by `@voyant-travel/utils`. Sorted alphabetically by the English
30
+ * display name for predictable scanning. Lifted to module scope so
31
+ * the array reference is stable across re-renders (otherwise the
32
+ * Select primitive's `items` identity changes every render and
33
+ * forces internal state churn).
34
+ */
35
+ const LANGUAGE_ITEMS = Object.entries(languages)
36
+ .map(([code, label]) => ({ value: code, label: `${label} (${code})` }))
37
+ .sort((a, b) => a.label.localeCompare(b.label));
38
+ export function TemplateDialog({ open, onOpenChange, template, onSuccess }) {
39
+ const isEditing = !!template;
40
+ const t = useOperatorAdminMessages().legal.templateDialog;
41
+ const { create, update } = useLegalContractTemplateMutation();
42
+ const validationByCode = {
43
+ nameRequired: t.validation.nameRequired,
44
+ slugRequired: t.validation.slugRequired,
45
+ slugKebabCase: t.validation.slugKebabCase,
46
+ bodyRequired: t.validation.bodyRequired,
47
+ };
48
+ const resolveValidation = (code) => (code && validationByCode[code]) || code || "";
49
+ const { variableCatalog, liquidSnippets } = useLegalContractTemplateAuthoring();
50
+ const [editorInstance, setEditorInstance] = useState(null);
51
+ const variableGroups = useMemo(() => variableCatalog.map((group) => ({
52
+ ...group,
53
+ variables: group.variables.map((variable) => ({
54
+ ...variable,
55
+ example: String(variable.example),
56
+ })),
57
+ })), [variableCatalog]);
58
+ const form = useForm({
59
+ resolver: zodResolver(templateFormSchema),
60
+ defaultValues: {
61
+ name: "",
62
+ slug: "",
63
+ scope: "customer",
64
+ language: "en",
65
+ description: "",
66
+ body: "",
67
+ active: true,
68
+ },
69
+ });
70
+ useEffect(() => {
71
+ if (open && template) {
72
+ form.reset({
73
+ name: template.name,
74
+ slug: template.slug,
75
+ scope: template.scope,
76
+ language: template.language,
77
+ description: template.description ?? "",
78
+ body: template.body,
79
+ active: template.active,
80
+ });
81
+ }
82
+ else if (open) {
83
+ form.reset();
84
+ }
85
+ }, [open, template, form]);
86
+ const onSubmit = async (values) => {
87
+ const payload = {
88
+ name: values.name,
89
+ slug: values.slug,
90
+ scope: values.scope,
91
+ language: values.language || "en",
92
+ description: values.description || undefined,
93
+ body: values.body,
94
+ active: values.active,
95
+ };
96
+ if (isEditing && template) {
97
+ await update.mutateAsync({ id: template.id, input: payload });
98
+ }
99
+ else {
100
+ await create.mutateAsync(payload);
101
+ }
102
+ onSuccess();
103
+ };
104
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.titleEdit : t.titleNew }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4 max-h-[70vh]", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: t.namePlaceholder }), form.formState.errors.name ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.name.message) })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.slugLabel }), _jsx(Input, { ...form.register("slug"), placeholder: t.slugPlaceholder }), form.formState.errors.slug ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.slug.message) })) : null] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.scopeLabel }), _jsxs(Select, { items: SCOPE_VALUES.map((value) => ({ value, label: t.scopeOptions[value] })), value: form.watch("scope"), onValueChange: (value) => form.setValue("scope", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SCOPE_VALUES.map((value) => (_jsx(SelectItem, { value: value, children: t.scopeOptions[value] }, value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.languageLabel }), _jsxs(Select, { items: LANGUAGE_ITEMS, value: form.watch("language") ?? "en", onValueChange: (value) => form.setValue("language", value ?? "en", { shouldDirty: true }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: LANGUAGE_ITEMS.map((lang) => (_jsx(SelectItem, { value: lang.value, children: lang.label }, lang.value))) })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.descriptionLabel }), _jsx(Textarea, { ...form.register("description"), placeholder: t.descriptionPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.bodyLabel }), _jsx(RichTextEditor, { value: form.watch("body"), onChange: (value) => form.setValue("body", value, {
105
+ shouldDirty: true,
106
+ shouldTouch: true,
107
+ shouldValidate: true,
108
+ }), placeholder: t.bodyPlaceholder, enableVariables: true, onEditorReady: setEditorInstance }), form.formState.errors.body ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.body.message) })) : (_jsx("p", { className: "text-[11px] text-muted-foreground", children: t.bodyHelp }))] }), _jsx(ContractTemplateAuthoringHelp, { variableGroups: variableGroups, snippets: liquidSnippets, onInsertVariable: (variable) => {
109
+ if (!editorInstance)
110
+ return;
111
+ insertVariableToken(editorInstance, variable.key);
112
+ }, onInsertSnippet: (snippet) => {
113
+ if (!editorInstance)
114
+ return;
115
+ insertPlainText(editorInstance, snippet.code);
116
+ } }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { children: t.activeLabel })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.cancel }), _jsxs(Button, { type: "submit", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, isEditing ? t.saveChanges : t.createAction] })] })] })] }) }));
117
+ }
@@ -0,0 +1,9 @@
1
+ type TemplateVersionDialogProps = {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ templateId: string;
5
+ onSuccess: () => void;
6
+ };
7
+ export declare function TemplateVersionDialog({ open, onOpenChange, templateId, onSuccess, }: TemplateVersionDialogProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=template-version-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-version-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/template-version-dialog.tsx"],"names":[],"mappings":"AAsCA,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,SAAS,GACV,EAAE,0BAA0B,2CAoH5B"}
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useOperatorAdminMessages } from "@voyant-travel/admin";
3
+ import { Button, ContractTemplateAuthoringHelp, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, } from "@voyant-travel/ui/components";
4
+ import { RichTextEditor } from "@voyant-travel/ui/components/rich-text-editor";
5
+ import { insertPlainText, insertVariableToken, } from "@voyant-travel/ui/components/rich-text-variable-extension";
6
+ import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
7
+ import { Loader2 } from "lucide-react";
8
+ import { useEffect, useMemo, useState } from "react";
9
+ import { useForm } from "react-hook-form";
10
+ import { z } from "zod/v4";
11
+ import { useLegalContractTemplateAuthoring, useLegalContractTemplateVersionMutation, } from "../index.js";
12
+ const versionFormSchema = z.object({
13
+ body: z.string().min(1, "bodyRequired"),
14
+ changelog: z.string().optional(),
15
+ createdBy: z.string().optional(),
16
+ });
17
+ export function TemplateVersionDialog({ open, onOpenChange, templateId, onSuccess, }) {
18
+ const t = useOperatorAdminMessages().legal.templateVersionDialog;
19
+ const { create } = useLegalContractTemplateVersionMutation();
20
+ const { variableCatalog, liquidSnippets } = useLegalContractTemplateAuthoring();
21
+ const [editorInstance, setEditorInstance] = useState(null);
22
+ const resolveValidation = (code) => code === "bodyRequired" ? t.validation.bodyRequired : code || "";
23
+ const variableGroups = useMemo(() => variableCatalog.map((group) => ({
24
+ ...group,
25
+ variables: group.variables.map((variable) => ({
26
+ ...variable,
27
+ example: String(variable.example),
28
+ })),
29
+ })), [variableCatalog]);
30
+ const form = useForm({
31
+ resolver: zodResolver(versionFormSchema),
32
+ defaultValues: {
33
+ body: "",
34
+ changelog: "",
35
+ createdBy: "",
36
+ },
37
+ });
38
+ useEffect(() => {
39
+ if (open) {
40
+ form.reset();
41
+ }
42
+ }, [open, form]);
43
+ const onSubmit = async (values) => {
44
+ await create.mutateAsync({
45
+ templateId,
46
+ input: {
47
+ body: values.body,
48
+ changelog: values.changelog || undefined,
49
+ createdBy: values.createdBy || undefined,
50
+ },
51
+ });
52
+ onSuccess();
53
+ };
54
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t.titleNew }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.bodyLabel }), _jsx(RichTextEditor, { value: form.watch("body"), onChange: (value) => form.setValue("body", value, {
55
+ shouldDirty: true,
56
+ shouldTouch: true,
57
+ shouldValidate: true,
58
+ }), placeholder: t.bodyPlaceholder, enableVariables: true, onEditorReady: setEditorInstance }), form.formState.errors.body ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.body.message) })) : null] }), _jsx(ContractTemplateAuthoringHelp, { variableGroups: variableGroups, snippets: liquidSnippets, onInsertVariable: (variable) => {
59
+ if (!editorInstance)
60
+ return;
61
+ insertVariableToken(editorInstance, variable.key);
62
+ }, onInsertSnippet: (snippet) => {
63
+ if (!editorInstance)
64
+ return;
65
+ insertPlainText(editorInstance, snippet.code);
66
+ } }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.changelogLabel }), _jsx(Input, { ...form.register("changelog"), placeholder: t.changelogPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.createdByLabel }), _jsx(Input, { ...form.register("createdBy"), placeholder: t.createdByPlaceholder })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.cancel }), _jsxs(Button, { type: "submit", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, t.createAction] })] })] })] }) }));
67
+ }