@voyant-travel/notifications-react 0.111.7

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 (178) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +30 -0
  3. package/dist/admin/index.d.ts +70 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +104 -0
  6. package/dist/admin/notification-deliveries-host.d.ts +7 -0
  7. package/dist/admin/notification-deliveries-host.d.ts.map +1 -0
  8. package/dist/admin/notification-deliveries-host.js +92 -0
  9. package/dist/admin/notification-delivery-detail-dialog.d.ts +8 -0
  10. package/dist/admin/notification-delivery-detail-dialog.d.ts.map +1 -0
  11. package/dist/admin/notification-delivery-detail-dialog.js +30 -0
  12. package/dist/admin/notification-reminder-rule-detail-host.d.ts +12 -0
  13. package/dist/admin/notification-reminder-rule-detail-host.d.ts.map +1 -0
  14. package/dist/admin/notification-reminder-rule-detail-host.js +23 -0
  15. package/dist/admin/notification-reminder-rule-dialog.d.ts +10 -0
  16. package/dist/admin/notification-reminder-rule-dialog.d.ts.map +1 -0
  17. package/dist/admin/notification-reminder-rule-dialog.js +122 -0
  18. package/dist/admin/notification-reminder-rules-host.d.ts +8 -0
  19. package/dist/admin/notification-reminder-rules-host.d.ts.map +1 -0
  20. package/dist/admin/notification-reminder-rules-host.js +57 -0
  21. package/dist/admin/notification-reminder-runs-host.d.ts +7 -0
  22. package/dist/admin/notification-reminder-runs-host.d.ts.map +1 -0
  23. package/dist/admin/notification-reminder-runs-host.js +28 -0
  24. package/dist/admin/notification-settings-host.d.ts +7 -0
  25. package/dist/admin/notification-settings-host.d.ts.map +1 -0
  26. package/dist/admin/notification-settings-host.js +11 -0
  27. package/dist/admin/notification-template-attachments-field.d.ts +10 -0
  28. package/dist/admin/notification-template-attachments-field.d.ts.map +1 -0
  29. package/dist/admin/notification-template-attachments-field.js +6 -0
  30. package/dist/admin/notification-template-authoring-help.d.ts +25 -0
  31. package/dist/admin/notification-template-authoring-help.d.ts.map +1 -0
  32. package/dist/admin/notification-template-authoring-help.js +8 -0
  33. package/dist/admin/notification-template-detail-host.d.ts +11 -0
  34. package/dist/admin/notification-template-detail-host.d.ts.map +1 -0
  35. package/dist/admin/notification-template-detail-host.js +159 -0
  36. package/dist/admin/notification-template-dialog-utils.d.ts +53 -0
  37. package/dist/admin/notification-template-dialog-utils.d.ts.map +1 -0
  38. package/dist/admin/notification-template-dialog-utils.js +112 -0
  39. package/dist/admin/notification-template-dialog.d.ts +10 -0
  40. package/dist/admin/notification-template-dialog.d.ts.map +1 -0
  41. package/dist/admin/notification-template-dialog.js +253 -0
  42. package/dist/admin/notification-template-rendered-preview.d.ts +14 -0
  43. package/dist/admin/notification-template-rendered-preview.d.ts.map +1 -0
  44. package/dist/admin/notification-template-rendered-preview.js +6 -0
  45. package/dist/admin/notification-templates-host.d.ts +9 -0
  46. package/dist/admin/notification-templates-host.d.ts.map +1 -0
  47. package/dist/admin/notification-templates-host.js +52 -0
  48. package/dist/admin/notifications-admin-shared.d.ts +14 -0
  49. package/dist/admin/notifications-admin-shared.d.ts.map +1 -0
  50. package/dist/admin/notifications-admin-shared.js +17 -0
  51. package/dist/admin/pages/notification-reminder-rule-detail-page.d.ts +8 -0
  52. package/dist/admin/pages/notification-reminder-rule-detail-page.d.ts.map +1 -0
  53. package/dist/admin/pages/notification-reminder-rule-detail-page.js +10 -0
  54. package/dist/admin/pages/notification-template-detail-page.d.ts +7 -0
  55. package/dist/admin/pages/notification-template-detail-page.d.ts.map +1 -0
  56. package/dist/admin/pages/notification-template-detail-page.js +9 -0
  57. package/dist/admin/reminders-preview-host.d.ts +7 -0
  58. package/dist/admin/reminders-preview-host.d.ts.map +1 -0
  59. package/dist/admin/reminders-preview-host.js +13 -0
  60. package/dist/client.d.ts +14 -0
  61. package/dist/client.d.ts.map +1 -0
  62. package/dist/client.js +71 -0
  63. package/dist/components/notification-settings-form.d.ts +2 -0
  64. package/dist/components/notification-settings-form.d.ts.map +1 -0
  65. package/dist/components/notification-settings-form.js +66 -0
  66. package/dist/components/reminders-preview-list.d.ts +6 -0
  67. package/dist/components/reminders-preview-list.d.ts.map +1 -0
  68. package/dist/components/reminders-preview-list.js +19 -0
  69. package/dist/components/stage-channel-editor-dialog.d.ts +11 -0
  70. package/dist/components/stage-channel-editor-dialog.d.ts.map +1 -0
  71. package/dist/components/stage-channel-editor-dialog.js +77 -0
  72. package/dist/components/stage-channel-list.d.ts +6 -0
  73. package/dist/components/stage-channel-list.d.ts.map +1 -0
  74. package/dist/components/stage-channel-list.js +20 -0
  75. package/dist/components/stage-editor-dialog.d.ts +10 -0
  76. package/dist/components/stage-editor-dialog.d.ts.map +1 -0
  77. package/dist/components/stage-editor-dialog.js +104 -0
  78. package/dist/components/stage-list.d.ts +5 -0
  79. package/dist/components/stage-list.d.ts.map +1 -0
  80. package/dist/components/stage-list.js +34 -0
  81. package/dist/components/template-picker.d.ts +19 -0
  82. package/dist/components/template-picker.d.ts.map +1 -0
  83. package/dist/components/template-picker.js +26 -0
  84. package/dist/components/timezone-combobox.d.ts +9 -0
  85. package/dist/components/timezone-combobox.d.ts.map +1 -0
  86. package/dist/components/timezone-combobox.js +67 -0
  87. package/dist/hooks/index.d.ts +19 -0
  88. package/dist/hooks/index.d.ts.map +1 -0
  89. package/dist/hooks/index.js +19 -0
  90. package/dist/hooks/use-notification-deliveries.d.ts +39 -0
  91. package/dist/hooks/use-notification-deliveries.d.ts.map +1 -0
  92. package/dist/hooks/use-notification-deliveries.js +12 -0
  93. package/dist/hooks/use-notification-delivery-mutation.d.ts +32 -0
  94. package/dist/hooks/use-notification-delivery-mutation.d.ts.map +1 -0
  95. package/dist/hooks/use-notification-delivery-mutation.js +24 -0
  96. package/dist/hooks/use-notification-delivery.d.ts +32 -0
  97. package/dist/hooks/use-notification-delivery.d.ts.map +1 -0
  98. package/dist/hooks/use-notification-delivery.js +12 -0
  99. package/dist/hooks/use-notification-reminder-rule-mutation.d.ts +53 -0
  100. package/dist/hooks/use-notification-reminder-rule-mutation.d.ts.map +1 -0
  101. package/dist/hooks/use-notification-reminder-rule-mutation.js +31 -0
  102. package/dist/hooks/use-notification-reminder-rule.d.ts +19 -0
  103. package/dist/hooks/use-notification-reminder-rule.d.ts.map +1 -0
  104. package/dist/hooks/use-notification-reminder-rule.js +12 -0
  105. package/dist/hooks/use-notification-reminder-rules.d.ts +25 -0
  106. package/dist/hooks/use-notification-reminder-rules.d.ts.map +1 -0
  107. package/dist/hooks/use-notification-reminder-rules.js +12 -0
  108. package/dist/hooks/use-notification-reminder-runs.d.ts +56 -0
  109. package/dist/hooks/use-notification-reminder-runs.d.ts.map +1 -0
  110. package/dist/hooks/use-notification-reminder-runs.js +12 -0
  111. package/dist/hooks/use-notification-settings.d.ts +50 -0
  112. package/dist/hooks/use-notification-settings.d.ts.map +1 -0
  113. package/dist/hooks/use-notification-settings.js +26 -0
  114. package/dist/hooks/use-notification-template-authoring.d.ts +5 -0
  115. package/dist/hooks/use-notification-template-authoring.d.ts.map +1 -0
  116. package/dist/hooks/use-notification-template-authoring.js +8 -0
  117. package/dist/hooks/use-notification-template-mutation.d.ts +54 -0
  118. package/dist/hooks/use-notification-template-mutation.d.ts.map +1 -0
  119. package/dist/hooks/use-notification-template-mutation.js +31 -0
  120. package/dist/hooks/use-notification-template-tools.d.ts +80 -0
  121. package/dist/hooks/use-notification-template-tools.d.ts.map +1 -0
  122. package/dist/hooks/use-notification-template-tools.js +21 -0
  123. package/dist/hooks/use-notification-template.d.ts +20 -0
  124. package/dist/hooks/use-notification-template.d.ts.map +1 -0
  125. package/dist/hooks/use-notification-template.js +12 -0
  126. package/dist/hooks/use-notification-templates.d.ts +26 -0
  127. package/dist/hooks/use-notification-templates.d.ts.map +1 -0
  128. package/dist/hooks/use-notification-templates.js +12 -0
  129. package/dist/hooks/use-reminder-rule-stage-mutation.d.ts +93 -0
  130. package/dist/hooks/use-reminder-rule-stage-mutation.d.ts.map +1 -0
  131. package/dist/hooks/use-reminder-rule-stage-mutation.js +53 -0
  132. package/dist/hooks/use-reminder-rule-stages.d.ts +25 -0
  133. package/dist/hooks/use-reminder-rule-stages.d.ts.map +1 -0
  134. package/dist/hooks/use-reminder-rule-stages.js +12 -0
  135. package/dist/hooks/use-reminder-stage-channel-mutation.d.ts +48 -0
  136. package/dist/hooks/use-reminder-stage-channel-mutation.d.ts.map +1 -0
  137. package/dist/hooks/use-reminder-stage-channel-mutation.js +42 -0
  138. package/dist/hooks/use-reminder-stage-channels.d.ts +18 -0
  139. package/dist/hooks/use-reminder-stage-channels.d.ts.map +1 -0
  140. package/dist/hooks/use-reminder-stage-channels.js +12 -0
  141. package/dist/hooks/use-reminders-preview.d.ts +21 -0
  142. package/dist/hooks/use-reminders-preview.d.ts.map +1 -0
  143. package/dist/hooks/use-reminders-preview.js +12 -0
  144. package/dist/i18n/en.d.ts +3 -0
  145. package/dist/i18n/en.d.ts.map +1 -0
  146. package/dist/i18n/en.js +385 -0
  147. package/dist/i18n/index.d.ts +5 -0
  148. package/dist/i18n/index.d.ts.map +1 -0
  149. package/dist/i18n/index.js +3 -0
  150. package/dist/i18n/messages.d.ts +386 -0
  151. package/dist/i18n/messages.d.ts.map +1 -0
  152. package/dist/i18n/messages.js +1 -0
  153. package/dist/i18n/provider.d.ts +26 -0
  154. package/dist/i18n/provider.d.ts.map +1 -0
  155. package/dist/i18n/provider.js +44 -0
  156. package/dist/i18n/ro.d.ts +3 -0
  157. package/dist/i18n/ro.d.ts.map +1 -0
  158. package/dist/i18n/ro.js +385 -0
  159. package/dist/index.d.ts +7 -0
  160. package/dist/index.d.ts.map +1 -0
  161. package/dist/index.js +6 -0
  162. package/dist/provider.d.ts +2 -0
  163. package/dist/provider.d.ts.map +1 -0
  164. package/dist/provider.js +1 -0
  165. package/dist/query-keys.d.ts +72 -0
  166. package/dist/query-keys.d.ts.map +1 -0
  167. package/dist/query-keys.js +19 -0
  168. package/dist/query-options.d.ts +1252 -0
  169. package/dist/query-options.d.ts.map +1 -0
  170. package/dist/query-options.js +120 -0
  171. package/dist/schemas.d.ts +796 -0
  172. package/dist/schemas.d.ts.map +1 -0
  173. package/dist/schemas.js +173 -0
  174. package/dist/ui.d.ts +9 -0
  175. package/dist/ui.d.ts.map +1 -0
  176. package/dist/ui.js +8 -0
  177. package/package.json +138 -0
  178. package/src/styles.css +2 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-reminder-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/notification-reminder-rule-dialog.tsx"],"names":[],"mappings":"AAwBA,OAAO,EACL,KAAK,8BAA8B,EAGpC,MAAM,aAAa,CAAA;AAoCpB,KAAK,mCAAmC,GAAG;IACzC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,8BAA8B,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,8BAA8B,CAAC,EAC7C,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,SAAS,GACV,EAAE,mCAAmC,2CAkMrC"}
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } 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 { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
10
+ import { useNotificationReminderRuleMutation, useNotificationTemplates, } from "../index.js";
11
+ const reminderRuleFormSchema = z.object({
12
+ name: z.string().min(1, "Name is required"),
13
+ status: z.enum(["draft", "active", "archived"]).default("draft"),
14
+ targetType: z.enum([
15
+ "booking_confirmed",
16
+ "booking_payment_schedule",
17
+ "payment_complete",
18
+ "booking_cancelled_non_payment",
19
+ ]),
20
+ channel: z.enum(["email", "sms"]),
21
+ // Optional default template — stages own per-channel templates and
22
+ // override this. Empty string is normalized to null in the payload.
23
+ templateId: z.string().optional(),
24
+ });
25
+ const reminderTargetValues = [
26
+ "booking_confirmed",
27
+ "payment_complete",
28
+ "booking_cancelled_non_payment",
29
+ "booking_payment_schedule",
30
+ ];
31
+ function slugifyReminderRule(value) {
32
+ const slug = value
33
+ .trim()
34
+ .toLowerCase()
35
+ .replace(/[^a-z0-9]+/g, "-")
36
+ .replace(/^-+|-+$/g, "");
37
+ return slug || "notification-rule";
38
+ }
39
+ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuccess, }) {
40
+ const isEditing = Boolean(rule);
41
+ const messages = useNotificationsUiMessagesOrDefault();
42
+ const t = messages.admin.reminderRuleDialog;
43
+ const common = messages.admin.common;
44
+ const { create, update } = useNotificationReminderRuleMutation();
45
+ const form = useForm({
46
+ resolver: zodResolver(reminderRuleFormSchema),
47
+ defaultValues: {
48
+ name: "",
49
+ status: "draft",
50
+ targetType: "booking_payment_schedule",
51
+ channel: "email",
52
+ templateId: "",
53
+ },
54
+ });
55
+ const channel = form.watch("channel");
56
+ const { data: templates } = useNotificationTemplates({
57
+ channel,
58
+ status: "active",
59
+ limit: 100,
60
+ offset: 0,
61
+ });
62
+ useEffect(() => {
63
+ if (open && rule) {
64
+ const resolvedTemplateId = rule.templateId ??
65
+ (rule.templateSlug
66
+ ? ((templates?.data ?? []).find((template) => template.slug === rule.templateSlug)?.id ??
67
+ "")
68
+ : "");
69
+ form.reset({
70
+ name: rule.name,
71
+ status: rule.status,
72
+ targetType: rule.targetType === "invoice" ? "booking_payment_schedule" : rule.targetType,
73
+ channel: rule.channel,
74
+ templateId: resolvedTemplateId,
75
+ });
76
+ return;
77
+ }
78
+ if (open) {
79
+ form.reset();
80
+ }
81
+ }, [open, rule, form, templates?.data]);
82
+ const onSubmit = async (values) => {
83
+ const payload = {
84
+ name: values.name,
85
+ slug: rule?.slug ??
86
+ `${slugifyReminderRule(values.targetType)}-${slugifyReminderRule(values.name)}`,
87
+ status: values.status,
88
+ targetType: values.targetType,
89
+ channel: values.channel,
90
+ provider: null,
91
+ templateId: values.templateId ? values.templateId : null,
92
+ templateSlug: null,
93
+ isSystem: rule?.isSystem ?? false,
94
+ metadata: rule?.metadata ?? null,
95
+ };
96
+ if (isEditing && rule) {
97
+ await update.mutateAsync({ id: rule.id, input: payload });
98
+ }
99
+ else {
100
+ await create.mutateAsync(payload);
101
+ }
102
+ onSuccess();
103
+ };
104
+ const isPending = create.isPending || update.isPending;
105
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.editTitle : t.createTitle }) }), _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.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: t.namePlaceholder })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.targetLabel }), _jsxs(Select, { value: form.watch("targetType"), onValueChange: (value) => {
106
+ if (!value)
107
+ return;
108
+ form.setValue("targetType", value);
109
+ }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: reminderTargetValues.map((value) => (_jsx(SelectItem, { value: value, children: messages.admin.reminderRulesPage.targets[value] }, value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.statusLabel }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => {
110
+ if (!value)
111
+ return;
112
+ form.setValue("status", value);
113
+ }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "draft", children: common.statusDraft }), _jsx(SelectItem, { value: "active", children: common.statusActive }), _jsx(SelectItem, { value: "archived", children: common.statusArchived })] })] })] })] }), _jsx("div", { className: "grid gap-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.channelLabel }), _jsxs(Select, { value: form.watch("channel"), onValueChange: (value) => {
114
+ if (!value)
115
+ return;
116
+ form.setValue("channel", value);
117
+ }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "email", children: common.channelEmail }), _jsx(SelectItem, { value: "sms", children: common.channelSms })] })] })] }) }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.defaultTemplateLabel }), _jsxs(Select, { value: form.watch("templateId"), onValueChange: (value) => {
118
+ if (!value)
119
+ return;
120
+ form.setValue("templateId", value);
121
+ }, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: t.selectTemplatePlaceholder }) }), _jsx(SelectContent, { children: (templates?.data ?? []).map((template) => (_jsxs(SelectItem, { value: template.id, children: [template.name, " (", template.slug, ")"] }, template.id))) })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.defaultTemplateHint })] }), !isEditing ? (_jsxs("p", { className: "rounded-md border border-dashed bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: [t.stagesHintBefore, " ", _jsx("strong", { children: t.stagesHintAction }), " ", t.stagesHintAfter] })) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", disabled: isPending, children: [isPending ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? common.saveChanges : t.createRule] })] })] })] }) }));
122
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Packaged admin host for the reminder rules list page (packaged-admin RFC
3
+ * Phase 3). Zero-prop: list/filter state stays component-local and the
4
+ * per-rule "Manage stages" link resolves through the
5
+ * `notificationReminderRule.detail` semantic destination.
6
+ */
7
+ export declare function NotificationReminderRulesHost(): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=notification-reminder-rules-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-reminder-rules-host.d.ts","sourceRoot":"","sources":["../../src/admin/notification-reminder-rules-host.tsx"],"names":[],"mappings":"AAuCA;;;;;GAKG;AACH,wBAAgB,6BAA6B,4CA6K5C"}
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useAdminHref, useAdminNavigate } from "@voyant-travel/admin";
4
+ import { Badge, Button, buttonVariants, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyant-travel/ui/components";
5
+ import { Layers, Loader2, Pencil, Plus, Search } from "lucide-react";
6
+ import { useState } from "react";
7
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
8
+ import { useNotificationReminderRules, } from "../index.js";
9
+ import { NotificationReminderRuleDialog } from "./notification-reminder-rule-dialog.js";
10
+ import { DestinationLink } from "./notifications-admin-shared.js";
11
+ const reminderTargetKeys = [
12
+ "booking_confirmed",
13
+ "booking_payment_schedule",
14
+ "payment_complete",
15
+ "booking_cancelled_non_payment",
16
+ ];
17
+ function getReminderTargetLabel(targets, targetType) {
18
+ return targets[targetType] ?? targetType;
19
+ }
20
+ /**
21
+ * Packaged admin host for the reminder rules list page (packaged-admin RFC
22
+ * Phase 3). Zero-prop: list/filter state stays component-local and the
23
+ * per-rule "Manage stages" link resolves through the
24
+ * `notificationReminderRule.detail` semantic destination.
25
+ */
26
+ export function NotificationReminderRulesHost() {
27
+ const messages = useNotificationsUiMessagesOrDefault();
28
+ const t = messages.admin.reminderRulesPage;
29
+ const common = messages.admin.common;
30
+ const resolveHref = useAdminHref();
31
+ const navigateTo = useAdminNavigate();
32
+ const [search, setSearch] = useState("");
33
+ const [channel, setChannel] = useState("all");
34
+ const [status, setStatus] = useState("all");
35
+ const [targetType, setTargetType] = useState("all");
36
+ const [dialogOpen, setDialogOpen] = useState(false);
37
+ const [editing, setEditing] = useState();
38
+ const { data, isPending, refetch } = useNotificationReminderRules({
39
+ search,
40
+ channel: channel === "all" ? undefined : channel,
41
+ status: status === "all" ? undefined : status,
42
+ targetType: targetType === "all" ? undefined : targetType,
43
+ });
44
+ return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: t.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: t.description })] }), _jsxs(Button, { onClick: () => {
45
+ setEditing(undefined);
46
+ setDialogOpen(true);
47
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), t.newRule] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "relative max-w-sm flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: t.searchPlaceholder, value: search, onChange: (event) => setSearch(event.target.value), className: "pl-9" })] }), _jsxs(Select, { value: targetType, onValueChange: (value) => setTargetType(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[190px]", children: _jsx(SelectValue, { placeholder: t.targetFilterPlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: t.allTargets }), reminderTargetKeys.map((value) => (_jsx(SelectItem, { value: value, children: t.targets[value] }, value)))] })] }), _jsxs(Select, { value: channel, onValueChange: (value) => setChannel(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[140px]", children: _jsx(SelectValue, { placeholder: common.channelFilterPlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: common.allChannels }), _jsx(SelectItem, { value: "email", children: common.channelEmail }), _jsx(SelectItem, { value: "sms", children: common.channelSms })] })] }), _jsxs(Select, { value: status, onValueChange: (value) => setStatus(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[140px]", children: _jsx(SelectValue, { placeholder: common.statusFilterPlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: common.allStatuses }), _jsx(SelectItem, { value: "draft", children: common.statusDraft }), _jsx(SelectItem, { value: "active", children: common.statusActive }), _jsx(SelectItem, { value: "archived", children: common.statusArchived })] })] })] }), isPending ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) })) : null, !isPending && (!data?.data || data.data.length === 0) ? (_jsx("div", { className: "rounded-md border border-dashed p-8 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: t.empty }) })) : null, !isPending && data?.data && data.data.length > 0 ? (_jsx("div", { className: "rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase tracking-wide text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-4 py-3", children: "Rule" }), _jsx("th", { className: "px-4 py-3", children: "Target" }), _jsx("th", { className: "px-4 py-3", children: "Channel" }), _jsx("th", { className: "px-4 py-3", children: "Status" }), _jsx("th", { className: "px-4 py-3 text-right", children: "Actions" })] }) }), _jsx("tbody", { children: data.data.map((rule) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-4 py-3", children: _jsx("div", { className: "font-medium", children: rule.name }) }), _jsx("td", { className: "px-4 py-3", children: getReminderTargetLabel(t.targets, rule.targetType) }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: "outline", children: rule.channel }) }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: rule.status === "active" ? "default" : "secondary", children: rule.status }) }), _jsx("td", { className: "px-4 py-3 text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsxs(DestinationLink, { href: resolveHref("notificationReminderRule.detail", {
48
+ ruleId: rule.id,
49
+ }), onNavigate: () => navigateTo("notificationReminderRule.detail", { ruleId: rule.id }), className: buttonVariants({ variant: "ghost", size: "sm" }), children: [_jsx(Layers, { className: "mr-2 h-4 w-4" }), t.manageStages] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
50
+ setEditing(rule);
51
+ setDialogOpen(true);
52
+ }, children: _jsx(Pencil, { className: "h-4 w-4" }) })] }) })] }, rule.id))) })] }) })) : null, _jsx(NotificationReminderRuleDialog, { open: dialogOpen, onOpenChange: setDialogOpen, rule: editing, onSuccess: () => {
53
+ setDialogOpen(false);
54
+ setEditing(undefined);
55
+ void refetch();
56
+ } })] }));
57
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Packaged admin host for the reminder runs page (packaged-admin RFC
3
+ * Phase 3). Zero-prop, read-only: filter state stays component-local and
4
+ * there is no cross-route navigation.
5
+ */
6
+ export declare function NotificationReminderRunsHost(): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=notification-reminder-runs-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-reminder-runs-host.d.ts","sourceRoot":"","sources":["../../src/admin/notification-reminder-runs-host.tsx"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,wBAAgB,4BAA4B,4CA8F3C"}
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Badge, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyant-travel/ui/components";
4
+ import { Loader2 } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
7
+ import { useNotificationReminderRuns } from "../index.js";
8
+ /**
9
+ * Packaged admin host for the reminder runs page (packaged-admin RFC
10
+ * Phase 3). Zero-prop, read-only: filter state stays component-local and
11
+ * there is no cross-route navigation.
12
+ */
13
+ export function NotificationReminderRunsHost() {
14
+ const messages = useNotificationsUiMessagesOrDefault();
15
+ const t = messages.admin.reminderRunsPage;
16
+ const common = messages.admin.common;
17
+ const [status, setStatus] = useState("all");
18
+ const { data, isPending } = useNotificationReminderRuns({
19
+ status: status === "all" ? undefined : status,
20
+ limit: 50,
21
+ offset: 0,
22
+ });
23
+ return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: t.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: t.description })] }), _jsx("div", { className: "flex items-center gap-3", children: _jsxs(Select, { value: status, onValueChange: (value) => setStatus(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[160px]", children: _jsx(SelectValue, { placeholder: common.statusFilterPlaceholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: common.allStatuses }), _jsx(SelectItem, { value: "queued", children: common.statusQueued }), _jsx(SelectItem, { value: "processing", children: common.statusProcessing }), _jsx(SelectItem, { value: "sent", children: common.statusSent }), _jsx(SelectItem, { value: "skipped", children: common.statusSkipped }), _jsx(SelectItem, { value: "failed", children: common.statusFailed })] })] }) }), isPending ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) })) : null, !isPending && (!data?.data || data.data.length === 0) ? (_jsx("div", { className: "rounded-md border border-dashed p-8 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: t.empty }) })) : null, !isPending && data?.data && data.data.length > 0 ? (_jsx("div", { className: "rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase tracking-wide text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-4 py-3", children: "Rule" }), _jsx("th", { className: "px-4 py-3", children: "Target" }), _jsx("th", { className: "px-4 py-3", children: "Recipient" }), _jsx("th", { className: "px-4 py-3", children: "Status" }), _jsx("th", { className: "px-4 py-3", children: "Processed" })] }) }), _jsx("tbody", { children: data.data.map((run) => (_jsxs("tr", { className: "border-t", children: [_jsxs("td", { className: "px-4 py-3", children: [_jsx("div", { className: "font-medium", children: run.reminderRule.name }), _jsx("div", { className: "font-mono text-xs text-muted-foreground", children: run.reminderRule.slug })] }), _jsxs("td", { className: "px-4 py-3", children: [_jsx("div", { children: run.targetType }), _jsx("div", { className: "font-mono text-xs text-muted-foreground", children: run.targetId })] }), _jsx("td", { className: "px-4 py-3", children: run.recipient ?? "—" }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: run.status === "sent"
24
+ ? "default"
25
+ : run.status === "failed"
26
+ ? "destructive"
27
+ : "secondary", children: run.status }) }), _jsx("td", { className: "px-4 py-3", children: new Date(run.processedAt).toLocaleString() })] }, run.id))) })] }) })) : null] }));
28
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Packaged admin host for the tenant-wide notification settings page
3
+ * (packaged-admin RFC Phase 3). Zero-prop: the settings form owns its data
4
+ * wiring through `@voyant-travel/notifications-react`.
5
+ */
6
+ export declare function NotificationSettingsHost(): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=notification-settings-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-settings-host.d.ts","sourceRoot":"","sources":["../../src/admin/notification-settings-host.tsx"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,wBAAwB,4CAYvC"}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { NotificationSettingsForm } from "../components/notification-settings-form.js";
4
+ /**
5
+ * Packaged admin host for the tenant-wide notification settings page
6
+ * (packaged-admin RFC Phase 3). Zero-prop: the settings form owns its data
7
+ * wiring through `@voyant-travel/notifications-react`.
8
+ */
9
+ export function NotificationSettingsHost() {
10
+ return (_jsxs("div", { className: "container mx-auto space-y-6 p-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Notification settings" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Tenant-wide quiet hours, blackout dates, recipient rate limits, and suppression window." })] }), _jsx(NotificationSettingsForm, {})] }));
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { NotificationsUiMessages } from "../i18n/index.js";
2
+ import { type TemplateAttachment } from "./notification-template-dialog-utils.js";
3
+ type NotificationTemplateAttachmentsFieldProps = {
4
+ attachments: ReadonlyArray<TemplateAttachment>;
5
+ onAttachmentChange: (attachment: TemplateAttachment, checked: boolean) => void;
6
+ t: NotificationsUiMessages["admin"]["templateDialog"];
7
+ };
8
+ export declare function NotificationTemplateAttachmentsField({ attachments, onAttachmentChange, t, }: NotificationTemplateAttachmentsFieldProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=notification-template-attachments-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-attachments-field.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-attachments-field.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,yCAAyC,CAAA;AAEhD,KAAK,yCAAyC,GAAG;IAC/C,WAAW,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;IAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9E,CAAC,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAA;CACtD,CAAA;AAED,wBAAgB,oCAAoC,CAAC,EACnD,WAAW,EACX,kBAAkB,EAClB,CAAC,GACF,EAAE,yCAAyC,2CAuB3C"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Checkbox, Label } from "@voyant-travel/ui/components";
3
+ import { ATTACHMENT_VALUES, attachmentItemLabel, } from "./notification-template-dialog-utils.js";
4
+ export function NotificationTemplateAttachmentsField({ attachments, onAttachmentChange, t, }) {
5
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.attachmentsLabel }), _jsx("div", { className: "flex flex-wrap gap-3", children: ATTACHMENT_VALUES.map((value) => (_jsxs("div", { className: "flex h-9 items-center gap-2 rounded-md border px-3 text-sm", children: [_jsx(Checkbox, { id: `notification-template-attachment-${value}`, checked: attachments.includes(value), onCheckedChange: (checked) => onAttachmentChange(value, checked === true) }), _jsx(Label, { htmlFor: `notification-template-attachment-${value}`, className: "cursor-pointer text-sm font-normal", children: attachmentItemLabel(t, value) })] }, value))) })] }));
6
+ }
@@ -0,0 +1,25 @@
1
+ import type { NotificationLiquidSnippet, NotificationTemplateVariableCategory, NotificationTemplateVariableDefinition } from "@voyant-travel/notifications";
2
+ type NotificationTemplateAuthoringHelpProps = {
3
+ variableGroups: NotificationTemplateVariableCategory[];
4
+ snippets?: NotificationLiquidSnippet[];
5
+ onInsertVariable?: (variable: NotificationTemplateVariableDefinition) => void;
6
+ onInsertSnippet?: (snippet: NotificationLiquidSnippet) => void;
7
+ className?: string;
8
+ messages?: {
9
+ title?: string;
10
+ description?: string;
11
+ tabs?: {
12
+ variables?: string;
13
+ liquid?: string;
14
+ };
15
+ searchPlaceholder?: string;
16
+ noVariables?: string;
17
+ example?: string;
18
+ insert?: string;
19
+ liquidUsage?: string;
20
+ noLiquidSnippets?: string;
21
+ };
22
+ };
23
+ export declare function NotificationTemplateAuthoringHelp({ variableGroups, snippets, onInsertVariable, onInsertSnippet, className, messages, }: NotificationTemplateAuthoringHelpProps): import("react/jsx-runtime").JSX.Element;
24
+ export {};
25
+ //# sourceMappingURL=notification-template-authoring-help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-authoring-help.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-authoring-help.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,yBAAyB,EACzB,oCAAoC,EACpC,sCAAsC,EACvC,MAAM,8BAA8B,CAAA;AAWrC,KAAK,sCAAsC,GAAG;IAC5C,cAAc,EAAE,oCAAoC,EAAE,CAAA;IACtD,QAAQ,CAAC,EAAE,yBAAyB,EAAE,CAAA;IACtC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,sCAAsC,KAAK,IAAI,CAAA;IAC7E,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAA;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,IAAI,CAAC,EAAE;YACL,SAAS,CAAC,EAAE,MAAM,CAAA;YAClB,MAAM,CAAC,EAAE,MAAM,CAAA;SAChB,CAAA;QACD,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;CACF,CAAA;AAED,wBAAgB,iCAAiC,CAAC,EAChD,cAAc,EACd,QAAa,EACb,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,QAAQ,GACT,EAAE,sCAAsC,2CAiBxC"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ContractTemplateAuthoringHelp, } from "@voyant-travel/ui/components/contract-template-authoring-help";
4
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
5
+ export function NotificationTemplateAuthoringHelp({ variableGroups, snippets = [], onInsertVariable, onInsertSnippet, className, messages, }) {
6
+ const defaults = useNotificationsUiMessagesOrDefault().admin.authoringHelp;
7
+ return (_jsx(ContractTemplateAuthoringHelp, { className: className, title: messages?.title ?? defaults.title, description: messages?.description ?? defaults.description, messages: messages, variableGroups: variableGroups, snippets: snippets, onInsertVariable: onInsertVariable, onInsertSnippet: onInsertSnippet }));
8
+ }
@@ -0,0 +1,11 @@
1
+ export interface NotificationTemplateDetailHostProps {
2
+ id: string;
3
+ }
4
+ /**
5
+ * Packaged admin host for the notification template detail page
6
+ * (packaged-admin RFC Phase 3). Takes the template id as a prop — the host
7
+ * route file binds `Route.useParams()` onto it. Back links resolve through
8
+ * the `notificationTemplate.list` semantic destination.
9
+ */
10
+ export declare function NotificationTemplateDetailHost({ id }: NotificationTemplateDetailHostProps): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=notification-template-detail-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-detail-host.tsx"],"names":[],"mappings":"AAgGA,MAAM,WAAW,mCAAmC;IAClD,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,EAAE,EAAE,mCAAmC,2CAyVzF"}
@@ -0,0 +1,159 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useAdminHref, useAdminNavigate } from "@voyant-travel/admin";
4
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Label, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, } from "@voyant-travel/ui/components";
5
+ import { ArrowLeft, Loader2, Pencil } from "lucide-react";
6
+ import { lazy, Suspense, useMemo, useState } from "react";
7
+ import { toast } from "sonner";
8
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
9
+ import { useNotificationDeliveries, useNotificationTemplate, useNotificationTemplateAuthoring, useNotificationTemplateTools, } from "../index.js";
10
+ import { NotificationDeliveryDetailDialog } from "./notification-delivery-detail-dialog.js";
11
+ import { DestinationLink } from "./notifications-admin-shared.js";
12
+ // Lazy-load: the template dialog pulls the rich-text editor (tiptap +
13
+ // prosemirror). Keeping it out of the detail-page chunk means those modules
14
+ // only download when the user opens the edit dialog.
15
+ const NotificationTemplateDialog = lazy(() => import("./notification-template-dialog.js").then((m) => ({
16
+ default: m.NotificationTemplateDialog,
17
+ })));
18
+ function parsePath(path) {
19
+ return path
20
+ .replace(/\[(\d+)\]/g, ".$1")
21
+ .split(".")
22
+ .filter(Boolean);
23
+ }
24
+ function setDeepValue(target, path, value) {
25
+ const segments = parsePath(path);
26
+ let current = target;
27
+ for (let index = 0; index < segments.length; index += 1) {
28
+ const segment = segments[index];
29
+ const isLast = index === segments.length - 1;
30
+ const nextSegment = segments[index + 1];
31
+ const nextIsIndex = nextSegment ? /^\d+$/.test(nextSegment) : false;
32
+ if (Array.isArray(current)) {
33
+ const arrayIndex = Number(segment);
34
+ if (Number.isNaN(arrayIndex))
35
+ return;
36
+ if (isLast) {
37
+ current[arrayIndex] = value;
38
+ return;
39
+ }
40
+ if (current[arrayIndex] == null) {
41
+ current[arrayIndex] = nextIsIndex ? [] : {};
42
+ }
43
+ current = current[arrayIndex];
44
+ continue;
45
+ }
46
+ if (typeof current !== "object" || current == null)
47
+ return;
48
+ const record = current;
49
+ if (isLast) {
50
+ record[segment] = value;
51
+ return;
52
+ }
53
+ if (record[segment] == null) {
54
+ record[segment] = nextIsIndex ? [] : {};
55
+ }
56
+ current = record[segment];
57
+ }
58
+ }
59
+ function buildSamplePayload(variableGroups) {
60
+ const sample = {};
61
+ for (const group of variableGroups) {
62
+ for (const variable of group.variables) {
63
+ setDeepValue(sample, variable.key, variable.example);
64
+ }
65
+ }
66
+ return sample;
67
+ }
68
+ /**
69
+ * Packaged admin host for the notification template detail page
70
+ * (packaged-admin RFC Phase 3). Takes the template id as a prop — the host
71
+ * route file binds `Route.useParams()` onto it. Back links resolve through
72
+ * the `notificationTemplate.list` semantic destination.
73
+ */
74
+ export function NotificationTemplateDetailHost({ id }) {
75
+ const messages = useNotificationsUiMessagesOrDefault();
76
+ const t = messages.admin.templateDetail;
77
+ const common = messages.admin.common;
78
+ const [editOpen, setEditOpen] = useState(false);
79
+ const [previewDataInput, setPreviewDataInput] = useState("");
80
+ const [selectedDeliveryId, setSelectedDeliveryId] = useState(null);
81
+ const resolveHref = useAdminHref();
82
+ const navigateTo = useAdminNavigate();
83
+ const { data: template, isPending, error, refetch } = useNotificationTemplate(id);
84
+ const { variableCatalog } = useNotificationTemplateAuthoring();
85
+ const variableGroups = useMemo(() => variableCatalog.map((group) => ({
86
+ ...group,
87
+ variables: group.variables.map((variable) => ({
88
+ ...variable,
89
+ example: String(variable.example),
90
+ })),
91
+ })), [variableCatalog]);
92
+ const defaultPreviewData = useMemo(() => JSON.stringify(buildSamplePayload(variableGroups), null, 2), [variableGroups]);
93
+ const { preview } = useNotificationTemplateTools();
94
+ const deliveries = useNotificationDeliveries({
95
+ templateSlug: template?.slug,
96
+ limit: 20,
97
+ offset: 0,
98
+ enabled: Boolean(template?.slug),
99
+ });
100
+ const parsePreviewData = () => {
101
+ try {
102
+ const parsed = previewDataInput.trim() ? JSON.parse(previewDataInput) : {};
103
+ if (typeof parsed !== "object" || parsed == null || Array.isArray(parsed)) {
104
+ throw new Error(common.previewDataNotObject);
105
+ }
106
+ return parsed;
107
+ }
108
+ catch (previewError) {
109
+ throw new Error(previewError instanceof Error ? previewError.message : common.previewInvalidJson);
110
+ }
111
+ };
112
+ const handlePreview = async () => {
113
+ if (!template)
114
+ return;
115
+ try {
116
+ const data = parsePreviewData();
117
+ await preview.mutateAsync({
118
+ channel: template.channel,
119
+ provider: null,
120
+ fromAddress: template.fromAddress,
121
+ subjectTemplate: template.subjectTemplate,
122
+ htmlTemplate: template.htmlTemplate,
123
+ textTemplate: template.textTemplate,
124
+ data,
125
+ });
126
+ }
127
+ catch (previewError) {
128
+ toast.error(previewError instanceof Error ? previewError.message : common.previewFailed);
129
+ }
130
+ };
131
+ if (isPending) {
132
+ return (_jsx("div", { className: "flex items-center justify-center py-16", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) }));
133
+ }
134
+ if (error || !template) {
135
+ return (_jsxs("div", { className: "flex flex-col gap-4 p-6", children: [_jsxs(DestinationLink, { href: resolveHref("notificationTemplate.list", {}), onNavigate: () => navigateTo("notificationTemplate.list", {}), className: "inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), t.backToTemplates] }), _jsx("div", { className: "rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive", children: error instanceof Error ? error.message : t.notFound })] }));
136
+ }
137
+ const renderedPreview = preview.data;
138
+ return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(DestinationLink, { href: resolveHref("notificationTemplate.list", {}), onNavigate: () => navigateTo("notificationTemplate.list", {}), className: "inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), t.backToTemplates] }), _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: template.name }), _jsx("p", { className: "font-mono text-xs text-muted-foreground", children: template.slug })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: template.channel }), _jsx(Badge, { variant: template.status === "active" ? "default" : "secondary", children: template.status })] })] }), _jsxs(Button, { onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), t.editTemplate] })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4", children: [_jsx(MetaCard, { label: t.metaChannel, value: template.channel }), _jsx(MetaCard, { label: t.metaFrom, value: template.fromAddress ?? common.defaultSender }), _jsx(MetaCard, { label: t.metaUpdated, value: new Date(template.updatedAt).toLocaleString() })] }), _jsxs(Tabs, { defaultValue: "overview", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "overview", children: t.tabOverview }), _jsx(TabsTrigger, { value: "preview", children: t.tabPreview }), _jsx(TabsTrigger, { value: "deliveries", children: t.recentDeliveries })] }), _jsx(TabsContent, { value: "overview", className: "mt-4 space-y-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.messageStructureTitle }) }), _jsxs(CardContent, { className: "space-y-3 text-sm", children: [_jsx(KeyValue, { label: t.subjectLabel, value: template.subjectTemplate ?? "—" }), _jsx(KeyValue, { label: t.textFallbackLabel, value: template.textTemplate ?? "—" }), _jsx(KeyValue, { label: t.descriptionLabel, value: template.metadata ? JSON.stringify(template.metadata) : "—" })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.htmlBodyTitle }) }), _jsx(CardContent, { children: template.htmlTemplate ? (_jsx("div", { className: "prose prose-sm max-w-none rounded-md border bg-background px-4 py-4 dark:prose-invert",
139
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Notification template HTML is rendered for preview. -- owner: notifications-react; existing suppression is intentional pending typed cleanup.
140
+ dangerouslySetInnerHTML: { __html: template.htmlTemplate } })) : (_jsx("div", { className: "rounded-md border bg-muted/20 px-4 py-3 text-sm text-muted-foreground", children: t.noHtmlConfigured })) })] })] }) }), _jsx(TabsContent, { value: "preview", className: "mt-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.sampleDataTitle }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsx(Label, { children: t.customJsonLabel }), _jsx(Textarea, { value: previewDataInput || defaultPreviewData, onChange: (event) => setPreviewDataInput(event.target.value), rows: 16, className: "font-mono text-xs" }), _jsxs(Button, { type: "button", variant: "outline", onClick: handlePreview, disabled: preview.isPending, children: [preview.isPending ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, t.renderPreview] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.renderedOutputTitle }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsx(KeyValue, { label: t.subjectLabel, value: renderedPreview?.subject ?? t.notRenderedYet }), template.channel === "email" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.htmlBodyTitle }), renderedPreview?.html ? (_jsx("div", { className: "prose prose-sm max-w-none rounded-md border bg-background px-4 py-4 dark:prose-invert",
141
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Rendered preview HTML is generated server-side for preview. -- owner: notifications-react; existing suppression is intentional pending typed cleanup.
142
+ dangerouslySetInnerHTML: { __html: renderedPreview.html } })) : (_jsx("div", { className: "rounded-md border bg-muted/20 px-4 py-3 text-sm text-muted-foreground", children: t.noRenderedHtml }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.textFallbackLabel }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: renderedPreview?.text ?? t.noRenderedText })] })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.smsBodyLabel }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: renderedPreview?.text ?? t.noRenderedText })] }))] })] })] }) }), _jsx(TabsContent, { value: "deliveries", className: "mt-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.recentDeliveries }) }), _jsx(CardContent, { children: deliveries.isPending ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) })) : deliveries.data?.data && deliveries.data.data.length > 0 ? (_jsx("div", { className: "rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase tracking-wide text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-4 py-3", children: "Recipient" }), _jsx("th", { className: "px-4 py-3", children: "Provider" }), _jsx("th", { className: "px-4 py-3", children: "Status" }), _jsx("th", { className: "px-4 py-3", children: "Created" }), _jsx("th", { className: "px-4 py-3 text-right", children: "View" })] }) }), _jsx("tbody", { children: deliveries.data.data.map((delivery) => (_jsxs("tr", { className: "border-t", children: [_jsxs("td", { className: "px-4 py-3", children: [_jsx("div", { children: delivery.toAddress }), delivery.subject ? (_jsx("div", { className: "text-xs text-muted-foreground", children: delivery.subject })) : null] }), _jsx("td", { className: "px-4 py-3", children: delivery.provider }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: delivery.status === "sent"
143
+ ? "default"
144
+ : delivery.status === "failed"
145
+ ? "destructive"
146
+ : "secondary", children: delivery.status }) }), _jsx("td", { className: "px-4 py-3", children: new Date(delivery.createdAt).toLocaleString() }), _jsx("td", { className: "px-4 py-3 text-right", children: _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setSelectedDeliveryId(delivery.id), children: t.inspect }) })] }, delivery.id))) })] }) })) : (_jsx("div", { className: "rounded-md border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: t.noDeliveriesForTemplate })) })] }) })] }), _jsx(Suspense, { fallback: null, children: _jsx(NotificationTemplateDialog, { open: editOpen, onOpenChange: setEditOpen, template: template, onSuccess: () => {
147
+ setEditOpen(false);
148
+ void refetch();
149
+ } }) }), _jsx(NotificationDeliveryDetailDialog, { deliveryId: selectedDeliveryId, open: Boolean(selectedDeliveryId), onOpenChange: (open) => {
150
+ if (!open)
151
+ setSelectedDeliveryId(null);
152
+ } })] }));
153
+ }
154
+ function MetaCard({ label, value }) {
155
+ return (_jsxs("div", { className: "rounded-md border p-4", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: "mt-2 text-sm", children: value })] }));
156
+ }
157
+ function KeyValue({ label, value }) {
158
+ return (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: "break-words text-sm", children: value })] }));
159
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from "zod/v4";
2
+ import type { NotificationsUiMessages } from "../i18n/index.js";
3
+ export declare const CHANNEL_VALUES: readonly ["email", "sms"];
4
+ export declare const STATUS_VALUES: readonly ["draft", "active", "archived"];
5
+ export declare const ATTACHMENT_VALUES: readonly ["contract", "invoice", "brochure"];
6
+ export declare const channelItemLabel: (t: NotificationsUiMessages["admin"]["common"], value: (typeof CHANNEL_VALUES)[number]) => string;
7
+ export declare const statusItemLabel: (t: NotificationsUiMessages["admin"]["common"], value: (typeof STATUS_VALUES)[number]) => string;
8
+ export declare const attachmentItemLabel: (t: NotificationsUiMessages["admin"]["templateDialog"], value: (typeof ATTACHMENT_VALUES)[number]) => string;
9
+ export declare const nativeSelectClassName = "h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30";
10
+ declare const templateAttachmentSchema: z.ZodEnum<{
11
+ invoice: "invoice";
12
+ contract: "contract";
13
+ brochure: "brochure";
14
+ }>;
15
+ export declare const templateFormSchema: z.ZodObject<{
16
+ name: z.ZodString;
17
+ slug: z.ZodString;
18
+ channel: z.ZodEnum<{
19
+ email: "email";
20
+ sms: "sms";
21
+ }>;
22
+ status: z.ZodDefault<z.ZodEnum<{
23
+ draft: "draft";
24
+ active: "active";
25
+ archived: "archived";
26
+ }>>;
27
+ subjectTemplate: z.ZodOptional<z.ZodString>;
28
+ htmlTemplate: z.ZodOptional<z.ZodString>;
29
+ textTemplate: z.ZodOptional<z.ZodString>;
30
+ fromAddress: z.ZodOptional<z.ZodString>;
31
+ attachments: z.ZodDefault<z.ZodArray<z.ZodEnum<{
32
+ invoice: "invoice";
33
+ contract: "contract";
34
+ brochure: "brochure";
35
+ }>>>;
36
+ active: z.ZodBoolean;
37
+ }, z.core.$strip>;
38
+ export type FormValues = z.input<typeof templateFormSchema>;
39
+ export type FormOutput = z.output<typeof templateFormSchema>;
40
+ export type TemplateAttachment = z.infer<typeof templateAttachmentSchema>;
41
+ export type InsertionTarget = "subject" | "body" | "text";
42
+ export declare function buildSamplePayload(variableGroups: Array<{
43
+ variables: Array<{
44
+ key: string;
45
+ example: string;
46
+ }>;
47
+ }>): Record<string, unknown>;
48
+ export declare function appendTemplateValue(current: string | undefined, addition: string): string;
49
+ export declare function variableReference(key: string): string;
50
+ export declare function readTemplateAttachments(metadata: unknown): TemplateAttachment[];
51
+ export declare function buildTemplateMetadata(metadata: unknown, attachments: ReadonlyArray<TemplateAttachment>): Record<string, unknown> | null;
52
+ export {};
53
+ //# sourceMappingURL=notification-template-dialog-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-dialog-utils.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-dialog-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAC1B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAE/D,eAAO,MAAM,cAAc,2BAA4B,CAAA;AACvD,eAAO,MAAM,aAAa,0CAA2C,CAAA;AACrE,eAAO,MAAM,iBAAiB,8CAA+C,CAAA;AAE7E,eAAO,MAAM,gBAAgB,GAC3B,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAC7C,OAAO,CAAC,OAAO,cAAc,EAAE,MAAM,CAAC,WACgB,CAAA;AAExD,eAAO,MAAM,eAAe,GAC1B,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAC7C,OAAO,CAAC,OAAO,aAAa,EAAE,MAAM,CAAC,WAC0D,CAAA;AAEjG,eAAO,MAAM,mBAAmB,GAC9B,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,EACrD,OAAO,CAAC,OAAO,iBAAiB,EAAE,MAAM,CAAC,WAMf,CAAA;AAE5B,eAAO,MAAM,qBAAqB,+QAC4O,CAAA;AAE9Q,QAAA,MAAM,wBAAwB;;;;EAA8C,CAAA;AAE5E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;iBAc7B,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC5D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACzE,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;AA8CzD,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,KAAK,CAAC;IACpB,SAAS,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD,CAAC,2BASH;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,UAGhF;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,UAE5C;AASD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,GAAG,kBAAkB,EAAE,CAW/E;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,OAAO,EACjB,WAAW,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhC"}