@voyantjs/notifications-react 0.106.0 → 0.108.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +30 -0
  2. package/dist/admin/index.d.ts +80 -0
  3. package/dist/admin/index.d.ts.map +1 -0
  4. package/dist/admin/index.js +102 -0
  5. package/dist/admin/notification-deliveries-host.d.ts +7 -0
  6. package/dist/admin/notification-deliveries-host.d.ts.map +1 -0
  7. package/dist/admin/notification-deliveries-host.js +92 -0
  8. package/dist/admin/notification-delivery-detail-dialog.d.ts +8 -0
  9. package/dist/admin/notification-delivery-detail-dialog.d.ts.map +1 -0
  10. package/dist/admin/notification-delivery-detail-dialog.js +30 -0
  11. package/dist/admin/notification-reminder-rule-detail-host.d.ts +12 -0
  12. package/dist/admin/notification-reminder-rule-detail-host.d.ts.map +1 -0
  13. package/dist/admin/notification-reminder-rule-detail-host.js +23 -0
  14. package/dist/admin/notification-reminder-rule-dialog.d.ts +10 -0
  15. package/dist/admin/notification-reminder-rule-dialog.d.ts.map +1 -0
  16. package/dist/admin/notification-reminder-rule-dialog.js +122 -0
  17. package/dist/admin/notification-reminder-rules-host.d.ts +8 -0
  18. package/dist/admin/notification-reminder-rules-host.d.ts.map +1 -0
  19. package/dist/admin/notification-reminder-rules-host.js +57 -0
  20. package/dist/admin/notification-reminder-runs-host.d.ts +7 -0
  21. package/dist/admin/notification-reminder-runs-host.d.ts.map +1 -0
  22. package/dist/admin/notification-reminder-runs-host.js +28 -0
  23. package/dist/admin/notification-settings-host.d.ts +7 -0
  24. package/dist/admin/notification-settings-host.d.ts.map +1 -0
  25. package/dist/admin/notification-settings-host.js +11 -0
  26. package/dist/admin/notification-template-authoring-help.d.ts +25 -0
  27. package/dist/admin/notification-template-authoring-help.d.ts.map +1 -0
  28. package/dist/admin/notification-template-authoring-help.js +8 -0
  29. package/dist/admin/notification-template-detail-host.d.ts +11 -0
  30. package/dist/admin/notification-template-detail-host.d.ts.map +1 -0
  31. package/dist/admin/notification-template-detail-host.js +159 -0
  32. package/dist/admin/notification-template-dialog.d.ts +10 -0
  33. package/dist/admin/notification-template-dialog.d.ts.map +1 -0
  34. package/dist/admin/notification-template-dialog.js +364 -0
  35. package/dist/admin/notification-templates-host.d.ts +9 -0
  36. package/dist/admin/notification-templates-host.d.ts.map +1 -0
  37. package/dist/admin/notification-templates-host.js +52 -0
  38. package/dist/admin/notifications-admin-shared.d.ts +14 -0
  39. package/dist/admin/notifications-admin-shared.d.ts.map +1 -0
  40. package/dist/admin/notifications-admin-shared.js +17 -0
  41. package/dist/admin/pages/notification-reminder-rule-detail-page.d.ts +8 -0
  42. package/dist/admin/pages/notification-reminder-rule-detail-page.d.ts.map +1 -0
  43. package/dist/admin/pages/notification-reminder-rule-detail-page.js +10 -0
  44. package/dist/admin/pages/notification-template-detail-page.d.ts +7 -0
  45. package/dist/admin/pages/notification-template-detail-page.d.ts.map +1 -0
  46. package/dist/admin/pages/notification-template-detail-page.js +9 -0
  47. package/dist/admin/reminders-preview-host.d.ts +7 -0
  48. package/dist/admin/reminders-preview-host.d.ts.map +1 -0
  49. package/dist/admin/reminders-preview-host.js +13 -0
  50. package/dist/components/notification-settings-form.d.ts +2 -0
  51. package/dist/components/notification-settings-form.d.ts.map +1 -0
  52. package/dist/components/notification-settings-form.js +66 -0
  53. package/dist/components/reminders-preview-list.d.ts +6 -0
  54. package/dist/components/reminders-preview-list.d.ts.map +1 -0
  55. package/dist/components/reminders-preview-list.js +19 -0
  56. package/dist/components/stage-channel-editor-dialog.d.ts +11 -0
  57. package/dist/components/stage-channel-editor-dialog.d.ts.map +1 -0
  58. package/dist/components/stage-channel-editor-dialog.js +77 -0
  59. package/dist/components/stage-channel-list.d.ts +6 -0
  60. package/dist/components/stage-channel-list.d.ts.map +1 -0
  61. package/dist/components/stage-channel-list.js +20 -0
  62. package/dist/components/stage-editor-dialog.d.ts +10 -0
  63. package/dist/components/stage-editor-dialog.d.ts.map +1 -0
  64. package/dist/components/stage-editor-dialog.js +104 -0
  65. package/dist/components/stage-list.d.ts +5 -0
  66. package/dist/components/stage-list.d.ts.map +1 -0
  67. package/dist/components/stage-list.js +34 -0
  68. package/dist/components/template-picker.d.ts +19 -0
  69. package/dist/components/template-picker.d.ts.map +1 -0
  70. package/dist/components/template-picker.js +26 -0
  71. package/dist/components/timezone-combobox.d.ts +9 -0
  72. package/dist/components/timezone-combobox.d.ts.map +1 -0
  73. package/dist/components/timezone-combobox.js +67 -0
  74. package/dist/i18n/en.d.ts +3 -0
  75. package/dist/i18n/en.d.ts.map +1 -0
  76. package/dist/i18n/en.js +385 -0
  77. package/dist/i18n/index.d.ts +5 -0
  78. package/dist/i18n/index.d.ts.map +1 -0
  79. package/dist/i18n/index.js +3 -0
  80. package/dist/i18n/messages.d.ts +386 -0
  81. package/dist/i18n/messages.d.ts.map +1 -0
  82. package/dist/i18n/messages.js +1 -0
  83. package/dist/i18n/provider.d.ts +26 -0
  84. package/dist/i18n/provider.d.ts.map +1 -0
  85. package/dist/i18n/provider.js +44 -0
  86. package/dist/i18n/ro.d.ts +3 -0
  87. package/dist/i18n/ro.d.ts.map +1 -0
  88. package/dist/i18n/ro.js +385 -0
  89. package/dist/ui.d.ts +9 -0
  90. package/dist/ui.d.ts.map +1 -0
  91. package/dist/ui.js +8 -0
  92. package/package.json +71 -10
  93. package/src/styles.css +2 -0
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @voyantjs/notifications-react
2
+
3
+ The notifications client tier: headless data hooks/clients plus the styled UI
4
+ components and admin pages (formerly `@voyantjs/notifications-ui`).
5
+
6
+ Headless consumers import from the root, `./hooks`, or `./client` — these pull
7
+ no styling peers. Styled surfaces live under `./ui`, `./components/*`,
8
+ `./admin`, `./i18n`, and `./styles.css`, whose heavier peers (`@voyantjs/ui`,
9
+ `@voyantjs/admin`, `lucide-react`, `react-hook-form`) are optional and only
10
+ needed when you import those subpaths.
11
+
12
+ React components for Voyant notifications: reminder sequence editor (stages + channels), notification settings, and preview.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add @voyantjs/notifications-react @voyantjs/ui @tanstack/react-query react react-dom
18
+ ```
19
+
20
+ ```ts
21
+ import "@voyantjs/notifications-react/styles.css"
22
+ ```
23
+
24
+ ## Components
25
+
26
+ - `<StageList />` — reminder rule's ordered stages with reorder + delete.
27
+ - `<StageEditorDialog />` — create / edit a stage (anchor, window, cadence).
28
+ - `<StageChannelEditorDialog />` — create / edit a channel under a stage.
29
+ - `<NotificationSettingsForm />` — quiet hours, blackout dates, weekend skip, recipient rate limit, suppression window.
30
+ - `<RemindersPreviewList />` — what would fire on a given date with reasoning.
@@ -0,0 +1,80 @@
1
+ import { type AdminExtension } from "@voyantjs/admin";
2
+ /**
3
+ * Semantic destinations the notifications admin surfaces navigate to
4
+ * (packaged-admin RFC §4.7): the templates list/detail pair (list rows and
5
+ * the detail page's back link) and the reminder rules list/detail pair
6
+ * (the per-rule "Manage stages" link and the stage editor's back link).
7
+ * All shapes are closed, so they are declared here directly.
8
+ */
9
+ declare module "@voyantjs/admin" {
10
+ interface AdminDestinations {
11
+ /** The notification templates list page. */
12
+ "notificationTemplate.list": Record<string, never>;
13
+ /** A notification template's detail page. */
14
+ "notificationTemplate.detail": {
15
+ templateId: string;
16
+ };
17
+ /** The reminder rules list page. */
18
+ "notificationReminderRule.list": Record<string, never>;
19
+ /** A reminder rule's detail page (stage sequence editor). */
20
+ "notificationReminderRule.detail": {
21
+ ruleId: string;
22
+ };
23
+ }
24
+ }
25
+ export { NotificationDeliveriesHost } from "./notification-deliveries-host.js";
26
+ export { NotificationDeliveryDetailDialog } from "./notification-delivery-detail-dialog.js";
27
+ export { NotificationReminderRuleDetailHost, type NotificationReminderRuleDetailHostProps, } from "./notification-reminder-rule-detail-host.js";
28
+ export { NotificationReminderRuleDialog } from "./notification-reminder-rule-dialog.js";
29
+ export { NotificationReminderRulesHost } from "./notification-reminder-rules-host.js";
30
+ export { NotificationReminderRunsHost } from "./notification-reminder-runs-host.js";
31
+ export { NotificationSettingsHost } from "./notification-settings-host.js";
32
+ export { NotificationTemplateAuthoringHelp } from "./notification-template-authoring-help.js";
33
+ export { NotificationTemplateDetailHost, type NotificationTemplateDetailHostProps, } from "./notification-template-detail-host.js";
34
+ export { NotificationTemplateDialog } from "./notification-template-dialog.js";
35
+ export { NotificationTemplatesHost } from "./notification-templates-host.js";
36
+ export { RemindersPreviewHost } from "./reminders-preview-host.js";
37
+ export interface CreateNotificationsAdminExtensionOptions {
38
+ /** Mount path of the notifications pages inside the admin workspace. Default `/notifications`. */
39
+ basePath?: string;
40
+ /** Localized page titles. Defaults are the English operator nav labels. */
41
+ labels?: {
42
+ templates?: string;
43
+ reminderRules?: string;
44
+ deliveries?: string;
45
+ reminderRuns?: string;
46
+ preview?: string;
47
+ settings?: string;
48
+ };
49
+ }
50
+ /**
51
+ * The notifications admin contribution (packaged-admin RFC Phase 3,
52
+ * `@voyantjs/<domain>-ui/admin` convention).
53
+ *
54
+ * NAVIGATION: deliberately none. The Notifications nav group (templates,
55
+ * reminder rules, deliveries, reminder runs, preview, settings) is part of
56
+ * the BASE operator navigation — see `createOperatorAdminNavigation` in
57
+ * `@voyantjs/admin` — so contributing nav entries here would duplicate
58
+ * them. If the base nav ever drops the notifications group, this extension
59
+ * is where the entries move.
60
+ *
61
+ * ROUTES: full implementations (packaged-admin RFC §4.8 endgame) — each
62
+ * contribution carries a lazy `page` module loader, so hosts bind them
63
+ * through their code-assembled admin route tree with no per-route files.
64
+ * The notifications pages keep their filter state component-local and fetch
65
+ * client-side, so contributions carry no loader, no search contract and no
66
+ * SSR override. {@link NotificationTemplatesHost},
67
+ * {@link NotificationReminderRulesHost}, {@link NotificationDeliveriesHost},
68
+ * {@link NotificationReminderRunsHost}, {@link RemindersPreviewHost} and
69
+ * {@link NotificationSettingsHost} are zero-prop; the detail contributions
70
+ * resolve wrapper pages (`./pages/*`) that bind the matched `$id` param onto
71
+ * {@link NotificationTemplateDetailHost} and
72
+ * {@link NotificationReminderRuleDetailHost}. Pages stay code-split because
73
+ * every `page` is a dynamic import of the specific host module, never a
74
+ * static reference from this factory.
75
+ *
76
+ * WIDGETS: none today. No cross-domain notifications card is slot-mounted —
77
+ * deliveries shown on other domains' detail pages are those hosts' concern.
78
+ */
79
+ export declare function createNotificationsAdminExtension(options?: CreateNotificationsAdminExtensionOptions): AdminExtension;
80
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA8C,MAAM,iBAAiB,CAAA;AAEjG;;;;;;GAMG;AACH,OAAO,QAAQ,iBAAiB,CAAC;IAC/B,UAAU,iBAAiB;QACzB,4CAA4C;QAC5C,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QAClD,6CAA6C;QAC7C,6BAA6B,EAAE;YAAE,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;QACrD,oCAAoC;QACpC,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QACtD,6DAA6D;QAC7D,iCAAiC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KACtD;CACF;AAKD,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAC9E,OAAO,EAAE,gCAAgC,EAAE,MAAM,0CAA0C,CAAA;AAC3F,OAAO,EACL,kCAAkC,EAClC,KAAK,uCAAuC,GAC7C,MAAM,6CAA6C,CAAA;AACpD,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAA;AACvF,OAAO,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAA;AACrF,OAAO,EAAE,4BAA4B,EAAE,MAAM,sCAAsC,CAAA;AACnF,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA;AAC1E,OAAO,EAAE,iCAAiC,EAAE,MAAM,2CAA2C,CAAA;AAC7F,OAAO,EACL,8BAA8B,EAC9B,KAAK,mCAAmC,GACzC,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAC9E,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAElE,MAAM,WAAW,wCAAwC;IACvD,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2EAA2E;IAC3E,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,GAAE,wCAA6C,GACrD,cAAc,CAkFhB"}
@@ -0,0 +1,102 @@
1
+ import { adminRoutePageModule, defineAdminExtension } from "@voyantjs/admin";
2
+ // Packaged admin hosts (packaged-admin RFC Phase 3): the operator-grade
3
+ // notifications pages bound to their data wiring + semantic-destination
4
+ // navigation. Host route files only bind route params onto these.
5
+ export { NotificationDeliveriesHost } from "./notification-deliveries-host.js";
6
+ export { NotificationDeliveryDetailDialog } from "./notification-delivery-detail-dialog.js";
7
+ export { NotificationReminderRuleDetailHost, } from "./notification-reminder-rule-detail-host.js";
8
+ export { NotificationReminderRuleDialog } from "./notification-reminder-rule-dialog.js";
9
+ export { NotificationReminderRulesHost } from "./notification-reminder-rules-host.js";
10
+ export { NotificationReminderRunsHost } from "./notification-reminder-runs-host.js";
11
+ export { NotificationSettingsHost } from "./notification-settings-host.js";
12
+ export { NotificationTemplateAuthoringHelp } from "./notification-template-authoring-help.js";
13
+ export { NotificationTemplateDetailHost, } from "./notification-template-detail-host.js";
14
+ export { NotificationTemplateDialog } from "./notification-template-dialog.js";
15
+ export { NotificationTemplatesHost } from "./notification-templates-host.js";
16
+ export { RemindersPreviewHost } from "./reminders-preview-host.js";
17
+ /**
18
+ * The notifications admin contribution (packaged-admin RFC Phase 3,
19
+ * `@voyantjs/<domain>-ui/admin` convention).
20
+ *
21
+ * NAVIGATION: deliberately none. The Notifications nav group (templates,
22
+ * reminder rules, deliveries, reminder runs, preview, settings) is part of
23
+ * the BASE operator navigation — see `createOperatorAdminNavigation` in
24
+ * `@voyantjs/admin` — so contributing nav entries here would duplicate
25
+ * them. If the base nav ever drops the notifications group, this extension
26
+ * is where the entries move.
27
+ *
28
+ * ROUTES: full implementations (packaged-admin RFC §4.8 endgame) — each
29
+ * contribution carries a lazy `page` module loader, so hosts bind them
30
+ * through their code-assembled admin route tree with no per-route files.
31
+ * The notifications pages keep their filter state component-local and fetch
32
+ * client-side, so contributions carry no loader, no search contract and no
33
+ * SSR override. {@link NotificationTemplatesHost},
34
+ * {@link NotificationReminderRulesHost}, {@link NotificationDeliveriesHost},
35
+ * {@link NotificationReminderRunsHost}, {@link RemindersPreviewHost} and
36
+ * {@link NotificationSettingsHost} are zero-prop; the detail contributions
37
+ * resolve wrapper pages (`./pages/*`) that bind the matched `$id` param onto
38
+ * {@link NotificationTemplateDetailHost} and
39
+ * {@link NotificationReminderRuleDetailHost}. Pages stay code-split because
40
+ * every `page` is a dynamic import of the specific host module, never a
41
+ * static reference from this factory.
42
+ *
43
+ * WIDGETS: none today. No cross-domain notifications card is slot-mounted —
44
+ * deliveries shown on other domains' detail pages are those hosts' concern.
45
+ */
46
+ export function createNotificationsAdminExtension(options = {}) {
47
+ const { basePath = "/notifications", labels = {} } = options;
48
+ const { templates = "Templates", reminderRules = "Reminder Rules", deliveries = "Deliveries", reminderRuns = "Reminder Runs", preview = "Preview", settings = "Settings", } = labels;
49
+ return defineAdminExtension({
50
+ id: "notifications",
51
+ routes: [
52
+ {
53
+ id: "notifications-templates-index",
54
+ path: `${basePath}/templates`,
55
+ title: templates,
56
+ page: () => import("./notification-templates-host.js").then((module) => adminRoutePageModule(module.NotificationTemplatesHost)),
57
+ },
58
+ {
59
+ id: "notifications-templates-detail",
60
+ path: `${basePath}/templates/$id`,
61
+ title: templates,
62
+ page: () => import("./pages/notification-template-detail-page.js"),
63
+ },
64
+ {
65
+ id: "notifications-reminder-rules-index",
66
+ path: `${basePath}/reminder-rules`,
67
+ title: reminderRules,
68
+ page: () => import("./notification-reminder-rules-host.js").then((module) => adminRoutePageModule(module.NotificationReminderRulesHost)),
69
+ },
70
+ {
71
+ id: "notifications-reminder-rules-detail",
72
+ path: `${basePath}/reminder-rules/$id`,
73
+ title: reminderRules,
74
+ page: () => import("./pages/notification-reminder-rule-detail-page.js"),
75
+ },
76
+ {
77
+ id: "notifications-deliveries",
78
+ path: `${basePath}/deliveries`,
79
+ title: deliveries,
80
+ page: () => import("./notification-deliveries-host.js").then((module) => adminRoutePageModule(module.NotificationDeliveriesHost)),
81
+ },
82
+ {
83
+ id: "notifications-reminder-runs",
84
+ path: `${basePath}/reminder-runs`,
85
+ title: reminderRuns,
86
+ page: () => import("./notification-reminder-runs-host.js").then((module) => adminRoutePageModule(module.NotificationReminderRunsHost)),
87
+ },
88
+ {
89
+ id: "notifications-preview",
90
+ path: `${basePath}/preview`,
91
+ title: preview,
92
+ page: () => import("./reminders-preview-host.js").then((module) => adminRoutePageModule(module.RemindersPreviewHost)),
93
+ },
94
+ {
95
+ id: "notifications-settings",
96
+ path: `${basePath}/settings`,
97
+ title: settings,
98
+ page: () => import("./notification-settings-host.js").then((module) => adminRoutePageModule(module.NotificationSettingsHost)),
99
+ },
100
+ ],
101
+ });
102
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Packaged admin host for the notification deliveries page (packaged-admin
3
+ * RFC Phase 3). Zero-prop: filter state stays component-local and the
4
+ * details dialog is in-page — no cross-route navigation.
5
+ */
6
+ export declare function NotificationDeliveriesHost(): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=notification-deliveries-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-deliveries-host.d.ts","sourceRoot":"","sources":["../../src/admin/notification-deliveries-host.tsx"],"names":[],"mappings":"AA2BA;;;;GAIG;AACH,wBAAgB,0BAA0B,4CA+KzC"}
@@ -0,0 +1,92 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Badge, Button, Dialog, DialogBody, DialogContent, DialogDescription, DialogHeader, DialogTitle, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
4
+ import { Loader2, RotateCcw, Search } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
7
+ import { useNotificationDeliveries, useNotificationDeliveryMutation, } from "../index.js";
8
+ /**
9
+ * Packaged admin host for the notification deliveries page (packaged-admin
10
+ * RFC Phase 3). Zero-prop: filter state stays component-local and the
11
+ * details dialog is in-page — no cross-route navigation.
12
+ */
13
+ export function NotificationDeliveriesHost() {
14
+ const messages = useNotificationsUiMessagesOrDefault();
15
+ const t = messages.admin.deliveriesPage;
16
+ const common = messages.admin.common;
17
+ const [channel, setChannel] = useState("all");
18
+ const [status, setStatus] = useState("all");
19
+ const [selectedDelivery, setSelectedDelivery] = useState(null);
20
+ const deliveryMutation = useNotificationDeliveryMutation();
21
+ const selectedDeliveryId = selectedDelivery?.id;
22
+ const { data, isPending } = useNotificationDeliveries({
23
+ channel: channel === "all" ? undefined : channel,
24
+ status: status === "all" ? undefined : status,
25
+ limit: 50,
26
+ offset: 0,
27
+ });
28
+ 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 })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_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: "pending", children: common.statusPending }), _jsx(SelectItem, { value: "sent", children: common.statusSent }), _jsx(SelectItem, { value: "failed", children: common.statusFailed }), _jsx(SelectItem, { value: "cancelled", children: common.statusCancelled })] })] })] }), 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: "To" }), _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: "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: "Logs" })] }) }), _jsx("tbody", { children: 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 font-mono text-xs", children: delivery.templateSlug ?? common.directTemplate }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: "outline", children: delivery.channel }) }), _jsx("td", { className: "px-4 py-3", children: delivery.provider }), _jsxs("td", { className: "px-4 py-3", children: [_jsx(Badge, { variant: delivery.status === "sent"
29
+ ? "default"
30
+ : delivery.status === "failed"
31
+ ? "destructive"
32
+ : "secondary", children: delivery.status }), delivery.status === "failed" && delivery.errorMessage ? (_jsx("div", { className: "mt-1 max-w-[280px] truncate text-destructive text-xs", children: delivery.errorMessage })) : null] }), _jsx("td", { className: "px-4 py-3", children: new Date(delivery.createdAt).toLocaleString() }), _jsx("td", { className: "px-4 py-3 text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [delivery.status === "failed" ? (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", disabled: deliveryMutation.resend.isPending, onClick: () => {
33
+ deliveryMutation.resend.mutate(delivery.id, {
34
+ onError(error) {
35
+ window.alert(error instanceof Error ? error.message : t.resendFailed);
36
+ },
37
+ });
38
+ }, children: [deliveryMutation.resend.isPending ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(RotateCcw, { className: "mr-2 h-4 w-4" })), t.resend] })) : null, _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setSelectedDelivery(delivery), children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), t.detailsButton] })] }) })] }, delivery.id))) })] }) })) : null, _jsx(DeliveryDetailsDialog, { messages: messages, delivery: selectedDelivery, open: Boolean(selectedDelivery), onOpenChange: (open) => {
39
+ if (!open)
40
+ setSelectedDelivery(null);
41
+ }, onResend: selectedDelivery?.status === "failed" && selectedDeliveryId
42
+ ? () => {
43
+ deliveryMutation.resend.mutate(selectedDeliveryId, {
44
+ onError(error) {
45
+ window.alert(error instanceof Error ? error.message : t.resendFailed);
46
+ },
47
+ });
48
+ }
49
+ : undefined, isResending: deliveryMutation.resend.isPending })] }));
50
+ }
51
+ function DeliveryDetailsDialog({ messages, delivery, open, onOpenChange, onResend, isResending = false, }) {
52
+ if (!delivery)
53
+ return null;
54
+ const t = messages.admin.deliveriesPage;
55
+ const failureLog = readRecord(delivery.metadata?.failureLog);
56
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "xl", children: [_jsx(DialogHeader, { children: _jsxs("div", { className: "flex items-start justify-between gap-4 pr-8", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(DialogTitle, { children: t.dialogTitle }), _jsx(DialogDescription, { children: t.dialogDescription })] }), onResend ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: isResending, onClick: onResend, children: [isResending ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(RotateCcw, { className: "mr-2 h-4 w-4" })), t.resend] })) : null] }) }), _jsxs(DialogBody, { className: "space-y-5", children: [_jsxs("section", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx(Detail, { label: t.labels.deliveryId, value: delivery.id, mono: true }), _jsx(Detail, { label: t.labels.status, value: delivery.status }), _jsx(Detail, { label: t.labels.provider, value: delivery.provider }), _jsx(Detail, { label: t.labels.providerMessageId, value: delivery.providerMessageId ?? "—", mono: true }), _jsx(Detail, { label: t.labels.template, value: delivery.templateSlug ?? messages.admin.common.directTemplate, mono: true }), _jsx(Detail, { label: t.labels.channel, value: delivery.channel }), _jsx(Detail, { label: t.labels.created, value: formatDateTime(delivery.createdAt) }), _jsx(Detail, { label: t.labels.failed, value: formatDateTime(delivery.failedAt) }), _jsx(Detail, { label: t.labels.sent, value: formatDateTime(delivery.sentAt) }), _jsx(Detail, { label: t.labels.scheduled, value: formatDateTime(delivery.scheduledFor) })] }), delivery.errorMessage ? (_jsx(LogSection, { title: t.errorMessageTitle, tone: "destructive", children: delivery.errorMessage })) : null, failureLog ? (_jsx(JsonSection, { title: t.failureLogTitle, value: failureLog })) : delivery.status === "failed" ? (_jsx(LogSection, { title: t.failureLogTitle, children: t.noFailureLog })) : null, _jsxs("section", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx(Detail, { label: t.labels.to, value: delivery.toAddress }), _jsx(Detail, { label: t.labels.from, value: delivery.fromAddress ?? "—" }), _jsx(Detail, { label: t.labels.subject, value: delivery.subject ?? "—" }), _jsx(Detail, { label: t.labels.target, value: formatTarget(delivery), mono: true })] }), _jsx(JsonSection, { title: t.payloadDataTitle, value: delivery.payloadData }), _jsx(JsonSection, { title: t.metadataTitle, value: delivery.metadata }), _jsx(BodySection, { title: t.textBodyTitle, value: delivery.textBody }), _jsx(BodySection, { title: t.htmlBodyTitle, value: delivery.htmlBody })] })] }) }));
57
+ }
58
+ function Detail({ label, value, mono = false }) {
59
+ return (_jsxs("div", { className: "rounded-md border bg-muted/20 p-3", children: [_jsx("div", { className: "text-muted-foreground text-xs uppercase tracking-wide", children: label }), _jsx("div", { className: `mt-1 break-words text-sm ${mono ? "font-mono" : ""}`, children: value || "—" })] }));
60
+ }
61
+ function LogSection({ title, children, tone, }) {
62
+ return (_jsxs("section", { className: "space-y-2", children: [_jsx("h2", { className: "font-medium text-sm", children: title }), _jsx("pre", { className: `max-h-56 overflow-auto rounded-md border p-3 text-xs ${
63
+ // i18n-literal-ok tailwind utilities keyed off a tone enum, not user-facing copy.
64
+ tone === "destructive" ? "border-destructive/30 bg-destructive/10" : "bg-muted/30"}`, children: children })] }));
65
+ }
66
+ function JsonSection({ title, value }) {
67
+ if (!value)
68
+ return null;
69
+ return _jsx(LogSection, { title: title, children: JSON.stringify(value, null, 2) });
70
+ }
71
+ function BodySection({ title, value }) {
72
+ if (!value)
73
+ return null;
74
+ return _jsx(LogSection, { title: title, children: value });
75
+ }
76
+ function readRecord(value) {
77
+ return value && typeof value === "object" && !Array.isArray(value)
78
+ ? value
79
+ : null;
80
+ }
81
+ function formatDateTime(value) {
82
+ return value ? new Date(value).toLocaleString() : "—";
83
+ }
84
+ function formatTarget(delivery) {
85
+ const targetId = delivery.bookingId ??
86
+ delivery.invoiceId ??
87
+ delivery.paymentSessionId ??
88
+ delivery.personId ??
89
+ delivery.organizationId ??
90
+ delivery.targetId;
91
+ return targetId ? `${delivery.targetType}:${targetId}` : delivery.targetType;
92
+ }
@@ -0,0 +1,8 @@
1
+ type NotificationDeliveryDetailDialogProps = {
2
+ deliveryId: string | null;
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ };
6
+ export declare function NotificationDeliveryDetailDialog({ deliveryId, open, onOpenChange, }: NotificationDeliveryDetailDialogProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=notification-delivery-detail-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-delivery-detail-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/notification-delivery-detail-dialog.tsx"],"names":[],"mappings":"AAeA,KAAK,qCAAqC,GAAG;IAC3C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACtC,CAAA;AAED,wBAAgB,gCAAgC,CAAC,EAC/C,UAAU,EACV,IAAI,EACJ,YAAY,GACb,EAAE,qCAAqC,2CA+HvC"}
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@voyantjs/ui/components";
4
+ import { Loader2 } from "lucide-react";
5
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
6
+ import { useNotificationDelivery } from "../index.js";
7
+ export function NotificationDeliveryDetailDialog({ deliveryId, open, onOpenChange, }) {
8
+ const messages = useNotificationsUiMessagesOrDefault();
9
+ const t = messages.admin.deliveryDetail;
10
+ const { data, isPending, error } = useNotificationDelivery(deliveryId ?? "", {
11
+ enabled: open && Boolean(deliveryId),
12
+ });
13
+ const delivery = data ?? null;
14
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-4xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t.title }) }), _jsxs("div", { className: "min-h-0 flex-1 space-y-4 overflow-y-auto py-4 pr-1", children: [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 && error ? (_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.loadFailed })) : null, !isPending && delivery ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-4", children: [_jsx(InfoCard, { label: t.labels.to, value: delivery.toAddress }), _jsx(InfoCard, { label: t.labels.template, value: delivery.templateSlug ?? messages.admin.common.directTemplate, mono: true }), _jsx(InfoCard, { label: t.labels.provider, value: delivery.provider }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.labels.status }), _jsx("div", { className: "mt-2", children: _jsx(Badge, { variant: delivery.status === "sent"
15
+ ? "default"
16
+ : delivery.status === "failed"
17
+ ? "destructive"
18
+ : "secondary", children: delivery.status }) })] })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs(Section, { title: t.metadataTitle, children: [_jsx(KeyValue, { label: t.labels.channel, value: delivery.channel }), _jsx(KeyValue, { label: t.labels.from, value: delivery.fromAddress ?? "—" }), _jsx(KeyValue, { label: t.labels.targetType, value: delivery.targetType }), _jsx(KeyValue, { label: t.labels.targetId, value: delivery.targetId ?? "—", mono: true }), _jsx(KeyValue, { label: t.labels.providerMessageId, value: delivery.providerMessageId ?? "—", mono: true }), _jsx(KeyValue, { label: t.labels.created, value: new Date(delivery.createdAt).toLocaleString() }), _jsx(KeyValue, { label: t.labels.sent, value: delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : "—" }), _jsx(KeyValue, { label: t.labels.failed, value: delivery.failedAt ? new Date(delivery.failedAt).toLocaleString() : "—" })] }), _jsxs(Section, { title: t.renderedPayloadTitle, children: [_jsx(KeyValue, { label: t.labels.subject, value: delivery.subject ?? "—" }), _jsx(KeyValue, { label: t.labels.error, value: delivery.errorMessage ?? "—" }), _jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.labels.text }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: delivery.textBody ?? "—" })] })] })] }), _jsx(Section, { title: t.htmlBodyTitle, children: delivery.htmlBody ? (_jsx("div", { className: "prose prose-sm max-w-none rounded-md border bg-background px-4 py-4 dark:prose-invert",
19
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Notification HTML body is stored template output rendered for preview.
20
+ dangerouslySetInnerHTML: { __html: delivery.htmlBody } })) : (_jsx("div", { className: "rounded-md border bg-muted/20 px-4 py-3 text-sm text-muted-foreground", children: t.noHtmlStored })) }), _jsx(Section, { title: t.payloadDataTitle, children: _jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: JSON.stringify(delivery.payloadData ?? {}, null, 2) }) })] })) : null] }), _jsx(DialogFooter, { children: _jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.close }) })] }) }));
21
+ }
22
+ function Section({ title, children }) {
23
+ return (_jsxs("section", { className: "space-y-3 rounded-md border p-4", children: [_jsx("h3", { className: "text-sm font-medium", children: title }), children] }));
24
+ }
25
+ function InfoCard({ label, value, mono = false, }) {
26
+ return (_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: `mt-2 break-words text-sm ${mono ? "font-mono text-xs" : ""}`, children: value })] }));
27
+ }
28
+ function KeyValue({ label, value, mono = false, }) {
29
+ return (_jsxs("div", { className: "grid gap-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: `break-words text-sm ${mono ? "font-mono text-xs" : ""}`, children: value })] }));
30
+ }
@@ -0,0 +1,12 @@
1
+ export interface NotificationReminderRuleDetailHostProps {
2
+ id: string;
3
+ }
4
+ /**
5
+ * Packaged admin host for the reminder rule detail page — rule summary plus
6
+ * the stage sequence editor (packaged-admin RFC Phase 3). Takes the rule id
7
+ * as a prop — the host route file binds `Route.useParams()` onto it. The
8
+ * back link resolves through the `notificationReminderRule.list` semantic
9
+ * destination.
10
+ */
11
+ export declare function NotificationReminderRuleDetailHost({ id, }: NotificationReminderRuleDetailHostProps): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=notification-reminder-rule-detail-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-reminder-rule-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/notification-reminder-rule-detail-host.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,uCAAuC;IACtD,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAAC,EACjD,EAAE,GACH,EAAE,uCAAuC,2CAuDzC"}
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useAdminHref, useAdminNavigate } from "@voyantjs/admin";
4
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
5
+ import { ArrowLeft } from "lucide-react";
6
+ import { StageList } from "../components/stage-list.js";
7
+ import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
8
+ import { useNotificationReminderRule } from "../index.js";
9
+ import { DestinationLink } from "./notifications-admin-shared.js";
10
+ /**
11
+ * Packaged admin host for the reminder rule detail page — rule summary plus
12
+ * the stage sequence editor (packaged-admin RFC Phase 3). Takes the rule id
13
+ * as a prop — the host route file binds `Route.useParams()` onto it. The
14
+ * back link resolves through the `notificationReminderRule.list` semantic
15
+ * destination.
16
+ */
17
+ export function NotificationReminderRuleDetailHost({ id, }) {
18
+ const messages = useNotificationsUiMessagesOrDefault();
19
+ const resolveHref = useAdminHref();
20
+ const navigateTo = useAdminNavigate();
21
+ const { data: rule, isLoading } = useNotificationReminderRule(id);
22
+ return (_jsxs("div", { className: "container mx-auto space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(DestinationLink, { href: resolveHref("notificationReminderRule.list", {}), onNavigate: () => navigateTo("notificationReminderRule.list", {}), children: _jsx(Button, { variant: "ghost", size: "icon", children: _jsx(ArrowLeft, { className: "size-4" }) }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-semibold", children: rule?.name ?? messages.admin.reminderRuleDetail.fallbackTitle }), _jsx("p", { className: "text-sm text-muted-foreground", children: isLoading ? messages.common.loading : rule?.slug })] })] }), rule && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: rule.targetType }), _jsx(Badge, { variant: "outline", children: rule.channel }), _jsx(Badge, { children: rule.status })] }))] }), rule && (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Rule" }) }), _jsxs(CardContent, { className: "text-sm grid grid-cols-2 gap-x-8 gap-y-2 text-muted-foreground", children: [_jsxs("div", { children: ["Template slug: ", _jsx("span", { className: "font-mono", children: rule.templateSlug ?? "—" })] }), _jsxs("div", { children: ["Template id: ", _jsx("span", { className: "font-mono", children: rule.templateId ?? "—" })] })] })] })), _jsx(StageList, { reminderRuleId: id })] }));
23
+ }
@@ -0,0 +1,10 @@
1
+ import { type NotificationReminderRuleRecord } from "../index.js";
2
+ type NotificationReminderRuleDialogProps = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ rule?: NotificationReminderRuleRecord;
6
+ onSuccess: () => void;
7
+ };
8
+ export declare function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuccess, }: NotificationReminderRuleDialogProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=notification-reminder-rule-dialog.d.ts.map
@@ -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 "@voyantjs/ui/components";
4
+ import { zodResolver } from "@voyantjs/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"}