@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,112 @@
1
+ import { z } from "zod/v4";
2
+ export const CHANNEL_VALUES = ["email", "sms"];
3
+ export const STATUS_VALUES = ["draft", "active", "archived"];
4
+ export const ATTACHMENT_VALUES = ["contract", "invoice", "brochure"];
5
+ export const channelItemLabel = (t, value) => (value === "email" ? t.channelEmail : t.channelSms);
6
+ export const statusItemLabel = (t, value) => (value === "draft" ? t.statusDraft : value === "active" ? t.statusActive : t.statusArchived);
7
+ export const attachmentItemLabel = (t, value) => value === "contract"
8
+ ? t.attachmentContract
9
+ : value === "invoice"
10
+ ? t.attachmentInvoice
11
+ : t.attachmentBrochure;
12
+ export 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";
13
+ const templateAttachmentSchema = z.enum(["contract", "invoice", "brochure"]);
14
+ export const templateFormSchema = z.object({
15
+ name: z.string().min(1, "Name is required"),
16
+ slug: z
17
+ .string()
18
+ .min(1, "Slug is required")
19
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Must be kebab-case"),
20
+ channel: z.enum(["email", "sms"]),
21
+ status: z.enum(["draft", "active", "archived"]).default("draft"),
22
+ subjectTemplate: z.string().optional(),
23
+ htmlTemplate: z.string().optional(),
24
+ textTemplate: z.string().optional(),
25
+ fromAddress: z.string().optional(),
26
+ attachments: z.array(templateAttachmentSchema).default([]),
27
+ active: z.boolean(),
28
+ });
29
+ function parsePath(path) {
30
+ return path
31
+ .replace(/\[(\d+)\]/g, ".$1")
32
+ .split(".")
33
+ .filter(Boolean);
34
+ }
35
+ function setDeepValue(target, path, value) {
36
+ const segments = parsePath(path);
37
+ let current = target;
38
+ for (let index = 0; index < segments.length; index += 1) {
39
+ const segment = segments[index];
40
+ const isLast = index === segments.length - 1;
41
+ const nextSegment = segments[index + 1];
42
+ const nextIsIndex = nextSegment ? /^\d+$/.test(nextSegment) : false;
43
+ if (Array.isArray(current)) {
44
+ const arrayIndex = Number(segment);
45
+ if (Number.isNaN(arrayIndex))
46
+ return;
47
+ if (isLast) {
48
+ current[arrayIndex] = value;
49
+ return;
50
+ }
51
+ if (current[arrayIndex] == null) {
52
+ current[arrayIndex] = nextIsIndex ? [] : {};
53
+ }
54
+ current = current[arrayIndex];
55
+ continue;
56
+ }
57
+ if (typeof current !== "object" || current == null)
58
+ return;
59
+ const record = current;
60
+ if (isLast) {
61
+ record[segment] = value;
62
+ return;
63
+ }
64
+ if (record[segment] == null) {
65
+ record[segment] = nextIsIndex ? [] : {};
66
+ }
67
+ current = record[segment];
68
+ }
69
+ }
70
+ export function buildSamplePayload(variableGroups) {
71
+ const sample = {};
72
+ for (const group of variableGroups) {
73
+ for (const variable of group.variables) {
74
+ setDeepValue(sample, variable.key, variable.example);
75
+ }
76
+ }
77
+ return sample;
78
+ }
79
+ export function appendTemplateValue(current, addition) {
80
+ if (!current?.trim())
81
+ return addition;
82
+ return `${current}${current.endsWith("\n") ? "" : "\n"}${addition}`;
83
+ }
84
+ export function variableReference(key) {
85
+ return `{{ ${key} }}`;
86
+ }
87
+ function getMetadataRecord(value) {
88
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
89
+ return null;
90
+ }
91
+ return value;
92
+ }
93
+ export function readTemplateAttachments(metadata) {
94
+ const record = getMetadataRecord(metadata);
95
+ const value = record?.attachments;
96
+ if (!Array.isArray(value)) {
97
+ return [];
98
+ }
99
+ const allowed = new Set(ATTACHMENT_VALUES);
100
+ return ATTACHMENT_VALUES.filter((attachment) => allowed.has(attachment) && value.includes(attachment));
101
+ }
102
+ export function buildTemplateMetadata(metadata, attachments) {
103
+ const current = getMetadataRecord(metadata);
104
+ const next = current ? { ...current } : {};
105
+ if (attachments.length > 0) {
106
+ next.attachments = [...attachments];
107
+ }
108
+ else {
109
+ delete next.attachments;
110
+ }
111
+ return Object.keys(next).length > 0 ? next : null;
112
+ }
@@ -0,0 +1,10 @@
1
+ import { type NotificationTemplateRecord } from "../index.js";
2
+ type NotificationTemplateDialogProps = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ template?: NotificationTemplateRecord;
6
+ onSuccess: () => void;
7
+ };
8
+ export declare function NotificationTemplateDialog(props: NotificationTemplateDialogProps): import("react/jsx-runtime").JSX.Element | null;
9
+ export {};
10
+ //# sourceMappingURL=notification-template-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-dialog.tsx"],"names":[],"mappings":"AAgCA,OAAO,EACL,KAAK,0BAA0B,EAIhC,MAAM,aAAa,CAAA;AAsBpB,KAAK,+BAA+B,GAAG;IACrC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,0BAA0B,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,+BAA+B,kDAMhF"}
@@ -0,0 +1,253 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { formatMessage } from "@voyant-travel/i18n";
4
+ import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, ScrollArea, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, } from "@voyant-travel/ui/components";
5
+ import { RichTextEditor } from "@voyant-travel/ui/components/rich-text-editor";
6
+ import { insertPlainText, insertVariableToken, } from "@voyant-travel/ui/components/rich-text-variable-extension";
7
+ import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
8
+ import { Loader2 } from "lucide-react";
9
+ import { useEffect, useMemo, useRef, useState } from "react";
10
+ import { useForm } from "react-hook-form";
11
+ import { toast } from "sonner";
12
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
13
+ import { useNotificationTemplateAuthoring, useNotificationTemplateMutation, useNotificationTemplateTools, } from "../index.js";
14
+ import { NotificationTemplateAttachmentsField } from "./notification-template-attachments-field.js";
15
+ import { NotificationTemplateAuthoringHelp } from "./notification-template-authoring-help.js";
16
+ import { appendTemplateValue, buildSamplePayload, buildTemplateMetadata, CHANNEL_VALUES, channelItemLabel, nativeSelectClassName, readTemplateAttachments, STATUS_VALUES, statusItemLabel, templateFormSchema, variableReference, } from "./notification-template-dialog-utils.js";
17
+ import { NotificationTemplateRenderedPreview } from "./notification-template-rendered-preview.js";
18
+ export function NotificationTemplateDialog(props) {
19
+ if (!props.open) {
20
+ return null;
21
+ }
22
+ return _jsx(NotificationTemplateDialogInner, { ...props });
23
+ }
24
+ function NotificationTemplateDialogInner({ open, onOpenChange, template, onSuccess, }) {
25
+ const isEditing = Boolean(template);
26
+ const messages = useNotificationsUiMessagesOrDefault();
27
+ const t = messages.admin.templateDialog;
28
+ const common = messages.admin.common;
29
+ const { create, update } = useNotificationTemplateMutation();
30
+ const { preview, testSend } = useNotificationTemplateTools();
31
+ const previewResetRef = useRef(preview.reset);
32
+ const testSendResetRef = useRef(testSend.reset);
33
+ const { variableCatalog, liquidSnippets } = useNotificationTemplateAuthoring();
34
+ const [editorInstance, setEditorInstance] = useState(null);
35
+ const [insertionTarget, setInsertionTarget] = useState("body");
36
+ const [previewDataInput, setPreviewDataInput] = useState("{}");
37
+ const [testRecipient, setTestRecipient] = useState("");
38
+ const variableGroups = useMemo(() => variableCatalog.map((group) => ({
39
+ ...group,
40
+ variables: group.variables.map((variable) => ({
41
+ ...variable,
42
+ example: String(variable.example),
43
+ })),
44
+ })), [variableCatalog]);
45
+ const defaultPreviewData = useMemo(() => JSON.stringify(buildSamplePayload(variableGroups), null, 2), [variableGroups]);
46
+ const form = useForm({
47
+ resolver: zodResolver(templateFormSchema),
48
+ defaultValues: {
49
+ name: "",
50
+ slug: "",
51
+ channel: "email",
52
+ status: "draft",
53
+ subjectTemplate: "",
54
+ htmlTemplate: "",
55
+ textTemplate: "",
56
+ fromAddress: "",
57
+ attachments: [],
58
+ active: true,
59
+ },
60
+ });
61
+ const channel = form.watch("channel");
62
+ const attachments = form.watch("attachments") ?? [];
63
+ previewResetRef.current = preview.reset;
64
+ testSendResetRef.current = testSend.reset;
65
+ useEffect(() => {
66
+ if (open && template) {
67
+ form.reset({
68
+ name: template.name,
69
+ slug: template.slug,
70
+ channel: template.channel,
71
+ status: template.status,
72
+ subjectTemplate: template.subjectTemplate ?? "",
73
+ htmlTemplate: template.htmlTemplate ?? "",
74
+ textTemplate: template.textTemplate ?? "",
75
+ fromAddress: template.fromAddress ?? "",
76
+ attachments: template.channel === "email" ? readTemplateAttachments(template.metadata) : [],
77
+ active: template.status === "active",
78
+ });
79
+ return;
80
+ }
81
+ if (open) {
82
+ form.reset();
83
+ }
84
+ }, [open, template, form]);
85
+ useEffect(() => {
86
+ if (!open)
87
+ return;
88
+ setInsertionTarget((current) => {
89
+ const next = channel === "sms" ? "text" : current === "text" ? "body" : current;
90
+ return next === current ? current : next;
91
+ });
92
+ }, [channel, open]);
93
+ useEffect(() => {
94
+ if (!open || channel === "email" || (form.getValues("attachments") ?? []).length === 0)
95
+ return;
96
+ form.setValue("attachments", [], {
97
+ shouldDirty: true,
98
+ shouldTouch: true,
99
+ shouldValidate: true,
100
+ });
101
+ }, [channel, form, open]);
102
+ useEffect(() => {
103
+ if (!open)
104
+ return;
105
+ setPreviewDataInput(defaultPreviewData);
106
+ setTestRecipient("");
107
+ previewResetRef.current();
108
+ testSendResetRef.current();
109
+ }, [defaultPreviewData, open]);
110
+ const onSubmit = async (values) => {
111
+ const payload = {
112
+ name: values.name,
113
+ slug: values.slug,
114
+ channel: values.channel,
115
+ provider: null,
116
+ status: values.active ? (values.status === "archived" ? "active" : values.status) : "draft",
117
+ subjectTemplate: values.channel === "email" ? values.subjectTemplate || null : null,
118
+ htmlTemplate: values.channel === "email" ? values.htmlTemplate || null : null,
119
+ textTemplate: values.channel === "sms" ? values.textTemplate || null : null,
120
+ fromAddress: values.channel === "email" ? values.fromAddress || null : null,
121
+ isSystem: template?.isSystem ?? false,
122
+ metadata: values.channel === "email"
123
+ ? buildTemplateMetadata(template?.metadata, values.attachments)
124
+ : buildTemplateMetadata(template?.metadata, []),
125
+ };
126
+ if (isEditing && template) {
127
+ await update.mutateAsync({ id: template.id, input: payload });
128
+ }
129
+ else {
130
+ await create.mutateAsync(payload);
131
+ }
132
+ onSuccess();
133
+ };
134
+ const isPending = create.isPending || update.isPending;
135
+ const parsePreviewData = () => {
136
+ try {
137
+ const parsed = previewDataInput.trim() ? JSON.parse(previewDataInput) : {};
138
+ if (typeof parsed !== "object" || parsed == null || Array.isArray(parsed)) {
139
+ throw new Error(common.previewDataNotObject);
140
+ }
141
+ return parsed;
142
+ }
143
+ catch (error) {
144
+ throw new Error(error instanceof Error ? error.message : common.previewInvalidJson);
145
+ }
146
+ };
147
+ const insertIntoTarget = (content, kind) => {
148
+ if (insertionTarget === "body" && channel === "email" && editorInstance) {
149
+ if (kind === "variable") {
150
+ insertVariableToken(editorInstance, content);
151
+ }
152
+ else {
153
+ insertPlainText(editorInstance, content);
154
+ }
155
+ return;
156
+ }
157
+ const fieldName = insertionTarget === "subject" ? "subjectTemplate" : "textTemplate";
158
+ const current = form.getValues(fieldName) ?? "";
159
+ const nextValue = kind === "variable"
160
+ ? // i18n-literal-ok single-space joiner between Liquid tokens, not user-facing copy.
161
+ `${current}${current ? " " : ""}${variableReference(content)}`
162
+ : appendTemplateValue(current, content);
163
+ form.setValue(fieldName, nextValue, {
164
+ shouldDirty: true,
165
+ shouldTouch: true,
166
+ shouldValidate: true,
167
+ });
168
+ };
169
+ const handlePreview = async () => {
170
+ try {
171
+ const data = parsePreviewData();
172
+ await preview.mutateAsync({
173
+ channel,
174
+ provider: null,
175
+ fromAddress: channel === "email" ? form.getValues("fromAddress") || null : null,
176
+ subjectTemplate: channel === "email" ? form.getValues("subjectTemplate") || null : null,
177
+ htmlTemplate: channel === "email" ? form.getValues("htmlTemplate") || null : null,
178
+ textTemplate: channel === "sms" ? form.getValues("textTemplate") || null : null,
179
+ data,
180
+ });
181
+ }
182
+ catch (error) {
183
+ toast.error(error instanceof Error ? error.message : common.previewFailed);
184
+ }
185
+ };
186
+ const handleTestSend = async () => {
187
+ if (!testRecipient.trim()) {
188
+ toast.error(channel === "email" ? t.recipientEmailRequired : t.recipientPhoneRequired);
189
+ return;
190
+ }
191
+ try {
192
+ const data = parsePreviewData();
193
+ await testSend.mutateAsync({
194
+ to: testRecipient.trim(),
195
+ channel,
196
+ provider: null,
197
+ from: channel === "email" ? form.getValues("fromAddress") || null : null,
198
+ subject: channel === "email" ? form.getValues("subjectTemplate") || null : null,
199
+ html: channel === "email" ? form.getValues("htmlTemplate") || null : null,
200
+ text: channel === "sms" ? form.getValues("textTemplate") || null : null,
201
+ data,
202
+ targetType: "other",
203
+ });
204
+ toast.success(channel === "email" ? t.testQueuedEmail : t.testQueuedSms);
205
+ }
206
+ catch (error) {
207
+ toast.error(error instanceof Error ? error.message : t.testSendFailed);
208
+ }
209
+ };
210
+ const setAttachmentSelected = (attachment, checked) => {
211
+ const current = form.getValues("attachments") ?? [];
212
+ const next = checked
213
+ ? [...current, attachment].filter((value, index, values) => values.indexOf(value) === index)
214
+ : current.filter((value) => value !== attachment);
215
+ form.setValue("attachments", next, {
216
+ shouldDirty: true,
217
+ shouldTouch: true,
218
+ shouldValidate: true,
219
+ });
220
+ };
221
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsx(DialogContent, { size: "xl", className: "h-[calc(100vh-2rem)]", children: _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "grid min-h-0 flex-1 grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.editTitle : t.createTitle }) }), _jsx(ScrollArea, { className: "min-h-0 flex-1", children: _jsxs("div", { className: "grid gap-4 py-4 pr-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.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: t.namePlaceholder }), form.formState.errors.name ? (_jsx("p", { className: "text-xs text-destructive", children: 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: 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.channelLabel }), _jsx("select", { className: nativeSelectClassName, value: form.watch("channel"), onChange: (event) => {
222
+ const nextChannel = event.target.value;
223
+ if (form.getValues("channel") === nextChannel)
224
+ return;
225
+ form.setValue("channel", nextChannel, {
226
+ shouldDirty: true,
227
+ shouldTouch: true,
228
+ shouldValidate: true,
229
+ });
230
+ }, children: CHANNEL_VALUES.map((value) => (_jsx("option", { value: value, children: channelItemLabel(common, value) }, value))) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.statusLabel }), _jsx("select", { className: nativeSelectClassName, value: form.watch("status"), onChange: (event) => {
231
+ const nextStatus = event.target.value;
232
+ if (form.getValues("status") === nextStatus)
233
+ return;
234
+ form.setValue("status", nextStatus, {
235
+ shouldDirty: true,
236
+ shouldTouch: true,
237
+ shouldValidate: true,
238
+ });
239
+ }, children: STATUS_VALUES.map((value) => (_jsx("option", { value: value, children: statusItemLabel(common, value) }, value))) })] })] }), channel === "email" ? (_jsxs(_Fragment, { children: [_jsx(NotificationTemplateAttachmentsField, { attachments: attachments, onAttachmentChange: setAttachmentSelected, t: t }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.fromAddressLabel }), _jsx(Input, { ...form.register("fromAddress"), placeholder: t.fromAddressPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.subjectLabel }), _jsx(Input, { ...form.register("subjectTemplate"), placeholder: t.subjectPlaceholder })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.htmlBodyLabel }), _jsx(RichTextEditor, { value: form.watch("htmlTemplate") ?? "", onChange: (value) => form.setValue("htmlTemplate", value, {
240
+ shouldDirty: true,
241
+ shouldTouch: true,
242
+ shouldValidate: true,
243
+ }), placeholder: t.htmlBodyPlaceholder, enableVariables: true, onEditorReady: setEditorInstance })] })] })) : null, channel === "sms" ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.smsBodyLabel }), _jsx(Textarea, { ...form.register("textTemplate"), placeholder: t.smsBodyPlaceholder, rows: 6, className: "font-mono text-xs" })] })) : null, _jsxs(Tabs, { defaultValue: "authoring", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "authoring", children: t.tabAuthoring }), _jsx(TabsTrigger, { value: "preview", children: t.tabPreview })] }), _jsxs(TabsContent, { value: "authoring", className: "mt-4 space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-[180px_1fr] sm:items-center", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { children: t.insertIntoLabel }), _jsxs("select", { className: nativeSelectClassName, value: insertionTarget, onChange: (event) => {
244
+ const nextTarget = event.target.value;
245
+ if (nextTarget === insertionTarget)
246
+ return;
247
+ setInsertionTarget(nextTarget);
248
+ }, children: [channel === "email" ? (_jsx("option", { value: "subject", children: t.insertTargetSubject })) : null, channel === "email" ? (_jsx("option", { value: "body", children: t.insertTargetHtmlBody })) : null, channel === "sms" ? (_jsx("option", { value: "text", children: t.insertTargetSmsBody })) : null] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.insertHint })] }), _jsx(NotificationTemplateAuthoringHelp, { variableGroups: variableGroups, snippets: liquidSnippets, onInsertVariable: (variable) => insertIntoTarget(variable.key, "variable"), onInsertSnippet: (snippet) => insertIntoTarget(snippet.code, "snippet") })] }), _jsx(TabsContent, { value: "preview", className: "mt-4 space-y-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_320px]", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.previewDataLabel }), _jsx(Textarea, { value: previewDataInput, onChange: (event) => setPreviewDataInput(event.target.value), rows: 14, className: "font-mono text-xs", placeholder: t.previewDataPlaceholder }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.previewDataHint })] }), _jsx("div", { className: "flex gap-2", children: _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.refreshPreview] }) }), _jsx(NotificationTemplateRenderedPreview, { channel: channel, data: preview.data, t: t })] }), _jsxs("div", { className: "space-y-4 rounded-md border p-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-sm font-medium", children: t.testSendTitle }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.testSendDescription })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: channel === "email" ? t.recipientEmailLabel : t.recipientPhoneLabel }), _jsx(Input, { value: testRecipient, onChange: (event) => setTestRecipient(event.target.value), placeholder: channel === "email"
249
+ ? t.recipientEmailPlaceholder
250
+ : t.recipientPhonePlaceholder })] }), _jsxs("div", { className: "space-y-1 text-xs text-muted-foreground", children: [_jsx("div", { children: t.providerAutoNote }), channel === "email" ? (_jsx("div", { children: formatMessage(t.fromNote, {
251
+ sender: form.watch("fromAddress") || common.defaultSender,
252
+ }) })) : null] }), _jsxs(Button, { type: "button", className: "w-full", onClick: handleTestSend, disabled: testSend.isPending, children: [testSend.isPending ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, channel === "email" ? t.sendTestEmail : t.sendTestSms] }), testSend.data ? (_jsxs("div", { className: "rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-xs text-emerald-200", children: ["Delivery queued with status ", _jsx("strong", { children: testSend.data.status }), testSend.data.provider ? ` via ${testSend.data.provider}` : "", "."] })) : null] })] }) })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { className: "cursor-pointer", children: t.markActiveLabel })] })] }) }), _jsxs(DialogFooter, { className: "mt-0", 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.createTemplate] })] })] }) }) }));
253
+ }
@@ -0,0 +1,14 @@
1
+ import type { NotificationsUiMessages } from "../i18n/index.js";
2
+ type RenderedPreviewData = {
3
+ subject?: string | null;
4
+ html?: string | null;
5
+ text?: string | null;
6
+ };
7
+ type NotificationTemplateRenderedPreviewProps = {
8
+ channel: "email" | "sms";
9
+ data: RenderedPreviewData | null | undefined;
10
+ t: NotificationsUiMessages["admin"]["templateDialog"];
11
+ };
12
+ export declare function NotificationTemplateRenderedPreview({ channel, data, t, }: NotificationTemplateRenderedPreviewProps): import("react/jsx-runtime").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=notification-template-rendered-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-rendered-preview.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-rendered-preview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAE/D,KAAK,mBAAmB,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,CAAA;AAED,KAAK,wCAAwC,GAAG;IAC9C,OAAO,EAAE,OAAO,GAAG,KAAK,CAAA;IACxB,IAAI,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,CAAA;IAC5C,CAAC,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAA;CACtD,CAAA;AAED,wBAAgB,mCAAmC,CAAC,EAClD,OAAO,EACP,IAAI,EACJ,CAAC,GACF,EAAE,wCAAwC,2CA6C1C"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function NotificationTemplateRenderedPreview({ channel, data, t, }) {
3
+ return (_jsxs("div", { className: "space-y-3 rounded-md border p-4", children: [_jsx("div", { className: "text-sm font-medium", children: t.renderedPreviewTitle }), channel === "email" ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedSubjectLabel }), _jsx("div", { className: "rounded-md border bg-muted/20 px-3 py-2 text-sm", children: data?.subject || t.noSubjectRendered })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedHtmlLabel }), _jsx("div", { className: "rounded-md border bg-background", children: data?.html ? (_jsx("div", { className: "prose prose-sm max-w-none px-3 py-3 dark:prose-invert",
4
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Preview HTML is generated server-side for template preview. -- owner: notifications-react; existing suppression is intentional pending typed cleanup.
5
+ dangerouslySetInnerHTML: { __html: data.html } })) : (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: t.noHtmlRendered })) })] })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedSmsLabel }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: data?.text || t.noSmsRendered })] }))] }));
6
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Packaged admin host for the notification templates list page
3
+ * (packaged-admin RFC Phase 3). Zero-prop: list/filter state stays
4
+ * component-local, row clicks resolve through the
5
+ * `notificationTemplate.detail` semantic destination, and the create/edit
6
+ * dialog stays lazily loaded inside the package.
7
+ */
8
+ export declare function NotificationTemplatesHost(): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=notification-templates-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-templates-host.d.ts","sourceRoot":"","sources":["../../src/admin/notification-templates-host.tsx"],"names":[],"mappings":"AAgCA;;;;;;GAMG;AACH,wBAAgB,yBAAyB,4CAuJxC"}
@@ -0,0 +1,52 @@
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, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyant-travel/ui/components";
5
+ import { Loader2, Pencil, Plus, Search } from "lucide-react";
6
+ import { lazy, Suspense, useState } from "react";
7
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
8
+ import { useNotificationTemplates, } from "../index.js";
9
+ import { DestinationLink } from "./notifications-admin-shared.js";
10
+ // Lazy-load: the template dialog pulls the rich-text editor (tiptap +
11
+ // prosemirror). Keeping it out of the list-page chunk means those modules
12
+ // only download when the user opens the create/edit dialog.
13
+ const NotificationTemplateDialog = lazy(() => import("./notification-template-dialog.js").then((m) => ({
14
+ default: m.NotificationTemplateDialog,
15
+ })));
16
+ /**
17
+ * Packaged admin host for the notification templates list page
18
+ * (packaged-admin RFC Phase 3). Zero-prop: list/filter state stays
19
+ * component-local, row clicks resolve through the
20
+ * `notificationTemplate.detail` semantic destination, and the create/edit
21
+ * dialog stays lazily loaded inside the package.
22
+ */
23
+ export function NotificationTemplatesHost() {
24
+ const messages = useNotificationsUiMessagesOrDefault();
25
+ const t = messages.admin.templatesPage;
26
+ const common = messages.admin.common;
27
+ const [search, setSearch] = useState("");
28
+ const [channel, setChannel] = useState("all");
29
+ const [status, setStatus] = useState("all");
30
+ const [dialogOpen, setDialogOpen] = useState(false);
31
+ const [editing, setEditing] = useState();
32
+ const resolveHref = useAdminHref();
33
+ const navigateTo = useAdminNavigate();
34
+ const { data, isPending, refetch } = useNotificationTemplates({
35
+ search,
36
+ channel: channel === "all" ? undefined : channel,
37
+ status: status === "all" ? undefined : status,
38
+ });
39
+ 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: () => {
40
+ setEditing(undefined);
41
+ setDialogOpen(true);
42
+ }, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), t.newTemplate] })] }), _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: 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: "Template" }), _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", children: "Updated" }), _jsx("th", { className: "px-4 py-3 text-right", children: "Actions" })] }) }), _jsx("tbody", { children: data.data.map((template) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-4 py-3", children: _jsxs(DestinationLink, { href: resolveHref("notificationTemplate.detail", {
43
+ templateId: template.id,
44
+ }), onNavigate: () => navigateTo("notificationTemplate.detail", { templateId: template.id }), className: "block rounded-sm outline-none transition-colors hover:text-primary focus-visible:text-primary", children: [_jsx("div", { className: "font-medium", children: template.name }), _jsx("div", { className: "font-mono text-xs text-muted-foreground", children: template.slug })] }) }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: "outline", children: template.channel }) }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: template.status === "active" ? "default" : "secondary", children: template.status }) }), _jsx("td", { className: "px-4 py-3", children: new Date(template.updatedAt).toLocaleString() }), _jsx("td", { className: "px-4 py-3 text-right", children: _jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
45
+ setEditing(template);
46
+ setDialogOpen(true);
47
+ }, children: _jsx(Pencil, { className: "h-4 w-4" }) }) })] }, template.id))) })] }) })) : null, _jsx(Suspense, { fallback: null, children: _jsx(NotificationTemplateDialog, { open: dialogOpen, onOpenChange: setDialogOpen, template: editing, onSuccess: () => {
48
+ setDialogOpen(false);
49
+ setEditing(undefined);
50
+ void refetch();
51
+ } }) })] }));
52
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * SPA-friendly destination link shared by the packaged notifications admin
4
+ * hosts: real href for a11y / middle-click, host-router navigation on plain
5
+ * left click (packaged-admin RFC §4.7 — hrefs come from `useAdminHref`,
6
+ * clicks go through `useAdminNavigate`).
7
+ */
8
+ export declare function DestinationLink({ href, onNavigate, className, children, }: {
9
+ href: string;
10
+ onNavigate: () => void;
11
+ className?: string;
12
+ children: ReactNode;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=notifications-admin-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications-admin-shared.d.ts","sourceRoot":"","sources":["../../src/admin/notifications-admin-shared.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,OAAO,CAAA;AAElD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,UAAU,EACV,SAAS,EACT,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;CACpB,2CAWA"}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * SPA-friendly destination link shared by the packaged notifications admin
5
+ * hosts: real href for a11y / middle-click, host-router navigation on plain
6
+ * left click (packaged-admin RFC §4.7 — hrefs come from `useAdminHref`,
7
+ * clicks go through `useAdminNavigate`).
8
+ */
9
+ export function DestinationLink({ href, onNavigate, className, children, }) {
10
+ const handleClick = (event) => {
11
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey)
12
+ return;
13
+ event.preventDefault();
14
+ onNavigate();
15
+ };
16
+ return (_jsx("a", { href: href, onClick: handleClick, className: className, children: children }));
17
+ }
@@ -0,0 +1,8 @@
1
+ import type { AdminRoutePageProps } from "@voyant-travel/admin";
2
+ /**
3
+ * Route page for the `notifications-reminder-rules-detail` contribution:
4
+ * binds the matched route's `$id` param onto
5
+ * {@link NotificationReminderRuleDetailHost}.
6
+ */
7
+ export default function NotificationReminderRuleDetailPage({ params }: AdminRoutePageProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=notification-reminder-rule-detail-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-reminder-rule-detail-page.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/notification-reminder-rule-detail-page.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAI/D;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,kCAAkC,CAAC,EAAE,MAAM,EAAE,EAAE,mBAAmB,2CAEzF"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { NotificationReminderRuleDetailHost } from "../notification-reminder-rule-detail-host.js";
3
+ /**
4
+ * Route page for the `notifications-reminder-rules-detail` contribution:
5
+ * binds the matched route's `$id` param onto
6
+ * {@link NotificationReminderRuleDetailHost}.
7
+ */
8
+ export default function NotificationReminderRuleDetailPage({ params }) {
9
+ return _jsx(NotificationReminderRuleDetailHost, { id: params.id ?? "" });
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { AdminRoutePageProps } from "@voyant-travel/admin";
2
+ /**
3
+ * Route page for the `notifications-templates-detail` contribution: binds
4
+ * the matched route's `$id` param onto {@link NotificationTemplateDetailHost}.
5
+ */
6
+ export default function NotificationTemplateDetailPage({ params }: AdminRoutePageProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=notification-template-detail-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-detail-page.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/notification-template-detail-page.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAI/D;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,8BAA8B,CAAC,EAAE,MAAM,EAAE,EAAE,mBAAmB,2CAErF"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { NotificationTemplateDetailHost } from "../notification-template-detail-host.js";
3
+ /**
4
+ * Route page for the `notifications-templates-detail` contribution: binds
5
+ * the matched route's `$id` param onto {@link NotificationTemplateDetailHost}.
6
+ */
7
+ export default function NotificationTemplateDetailPage({ params }) {
8
+ return _jsx(NotificationTemplateDetailHost, { id: params.id ?? "" });
9
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Packaged admin host for the read-only reminders preview page
3
+ * (packaged-admin RFC Phase 3). Zero-prop: the preview list owns its data
4
+ * wiring through `@voyant-travel/notifications-react`.
5
+ */
6
+ export declare function RemindersPreviewHost(): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=reminders-preview-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reminders-preview-host.d.ts","sourceRoot":"","sources":["../../src/admin/reminders-preview-host.tsx"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,oBAAoB,4CAYnC"}
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { RemindersPreviewList } from "../components/reminders-preview-list.js";
4
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
5
+ /**
6
+ * Packaged admin host for the read-only reminders preview page
7
+ * (packaged-admin RFC Phase 3). Zero-prop: the preview list owns its data
8
+ * wiring through `@voyant-travel/notifications-react`.
9
+ */
10
+ export function RemindersPreviewHost() {
11
+ const t = useNotificationsUiMessagesOrDefault().admin.previewPage;
12
+ return (_jsxs("div", { className: "container mx-auto space-y-6 p-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: t.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: t.description })] }), _jsx(RemindersPreviewList, {})] }));
13
+ }
@@ -0,0 +1,14 @@
1
+ import type { z } from "zod";
2
+ export type VoyantFetcher = (url: string, init?: RequestInit) => Promise<Response>;
3
+ export declare const defaultFetcher: VoyantFetcher;
4
+ export declare class VoyantApiError extends Error {
5
+ readonly status: number;
6
+ readonly body: unknown;
7
+ constructor(message: string, status: number, body: unknown);
8
+ }
9
+ export interface FetchWithValidationOptions {
10
+ baseUrl: string;
11
+ fetcher: VoyantFetcher;
12
+ }
13
+ export declare function fetchWithValidation<TOut>(path: string, schema: z.ZodType<TOut>, options: FetchWithValidationOptions, init?: RequestInit): Promise<TOut>;
14
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElF,eAAO,MAAM,cAAc,EAAE,aAIzB,CAAA;AAEJ,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAM3D;AAcD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EACvB,OAAO,EAAE,0BAA0B,EACnC,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CA8Bf"}