@voyantjs/notifications-react 0.106.0 → 0.107.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 (87) hide show
  1. package/README.md +30 -0
  2. package/dist/admin/index.d.ts +82 -0
  3. package/dist/admin/index.d.ts.map +1 -0
  4. package/dist/admin/index.js +96 -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/reminders-preview-host.d.ts +7 -0
  42. package/dist/admin/reminders-preview-host.d.ts.map +1 -0
  43. package/dist/admin/reminders-preview-host.js +13 -0
  44. package/dist/components/notification-settings-form.d.ts +2 -0
  45. package/dist/components/notification-settings-form.d.ts.map +1 -0
  46. package/dist/components/notification-settings-form.js +66 -0
  47. package/dist/components/reminders-preview-list.d.ts +6 -0
  48. package/dist/components/reminders-preview-list.d.ts.map +1 -0
  49. package/dist/components/reminders-preview-list.js +19 -0
  50. package/dist/components/stage-channel-editor-dialog.d.ts +11 -0
  51. package/dist/components/stage-channel-editor-dialog.d.ts.map +1 -0
  52. package/dist/components/stage-channel-editor-dialog.js +77 -0
  53. package/dist/components/stage-channel-list.d.ts +6 -0
  54. package/dist/components/stage-channel-list.d.ts.map +1 -0
  55. package/dist/components/stage-channel-list.js +20 -0
  56. package/dist/components/stage-editor-dialog.d.ts +10 -0
  57. package/dist/components/stage-editor-dialog.d.ts.map +1 -0
  58. package/dist/components/stage-editor-dialog.js +104 -0
  59. package/dist/components/stage-list.d.ts +5 -0
  60. package/dist/components/stage-list.d.ts.map +1 -0
  61. package/dist/components/stage-list.js +34 -0
  62. package/dist/components/template-picker.d.ts +19 -0
  63. package/dist/components/template-picker.d.ts.map +1 -0
  64. package/dist/components/template-picker.js +26 -0
  65. package/dist/components/timezone-combobox.d.ts +9 -0
  66. package/dist/components/timezone-combobox.d.ts.map +1 -0
  67. package/dist/components/timezone-combobox.js +67 -0
  68. package/dist/i18n/en.d.ts +3 -0
  69. package/dist/i18n/en.d.ts.map +1 -0
  70. package/dist/i18n/en.js +385 -0
  71. package/dist/i18n/index.d.ts +5 -0
  72. package/dist/i18n/index.d.ts.map +1 -0
  73. package/dist/i18n/index.js +3 -0
  74. package/dist/i18n/messages.d.ts +386 -0
  75. package/dist/i18n/messages.d.ts.map +1 -0
  76. package/dist/i18n/messages.js +1 -0
  77. package/dist/i18n/provider.d.ts +26 -0
  78. package/dist/i18n/provider.d.ts.map +1 -0
  79. package/dist/i18n/provider.js +44 -0
  80. package/dist/i18n/ro.d.ts +3 -0
  81. package/dist/i18n/ro.d.ts.map +1 -0
  82. package/dist/i18n/ro.js +385 -0
  83. package/dist/ui.d.ts +9 -0
  84. package/dist/ui.d.ts.map +1 -0
  85. package/dist/ui.js +8 -0
  86. package/package.json +71 -10
  87. 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,82 @@
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: contributions are metadata only — the notifications pages keep
62
+ * their filter state component-local, so there are no URL search contracts.
63
+ * The PAGES are package-owned: {@link NotificationTemplatesHost},
64
+ * {@link NotificationReminderRulesHost}, {@link NotificationDeliveriesHost},
65
+ * {@link NotificationReminderRunsHost}, {@link RemindersPreviewHost} and
66
+ * {@link NotificationSettingsHost} are zero-prop;
67
+ * {@link NotificationTemplateDetailHost} and
68
+ * {@link NotificationReminderRuleDetailHost} bind the detail pages to their
69
+ * data wiring and resolve every cross-route link through the semantic
70
+ * destinations declared above. `component:` is intentionally NOT attached
71
+ * to these contributions yet: the contribution contract renders zero-prop
72
+ * pages (route components read params via the router, per RFC §4.2), while
73
+ * the detail hosts take the record id as a prop. Host route files stay the
74
+ * thin binding layer (`Route.useParams()` → host props) until the §4.2
75
+ * code-based route assembly gives packaged pages a router-agnostic way to
76
+ * read route state.
77
+ *
78
+ * WIDGETS: none today. No cross-domain notifications card is slot-mounted —
79
+ * deliveries shown on other domains' detail pages are those hosts' concern.
80
+ */
81
+ export declare function createNotificationsAdminExtension(options?: CreateNotificationsAdminExtensionOptions): AdminExtension;
82
+ //# 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,EAAwB,MAAM,iBAAiB,CAAA;AAE3E;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,GAAE,wCAA6C,GACrD,cAAc,CAwDhB"}
@@ -0,0 +1,96 @@
1
+ import { 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: contributions are metadata only — the notifications pages keep
29
+ * their filter state component-local, so there are no URL search contracts.
30
+ * The PAGES are package-owned: {@link NotificationTemplatesHost},
31
+ * {@link NotificationReminderRulesHost}, {@link NotificationDeliveriesHost},
32
+ * {@link NotificationReminderRunsHost}, {@link RemindersPreviewHost} and
33
+ * {@link NotificationSettingsHost} are zero-prop;
34
+ * {@link NotificationTemplateDetailHost} and
35
+ * {@link NotificationReminderRuleDetailHost} bind the detail pages to their
36
+ * data wiring and resolve every cross-route link through the semantic
37
+ * destinations declared above. `component:` is intentionally NOT attached
38
+ * to these contributions yet: the contribution contract renders zero-prop
39
+ * pages (route components read params via the router, per RFC §4.2), while
40
+ * the detail hosts take the record id as a prop. Host route files stay the
41
+ * thin binding layer (`Route.useParams()` → host props) until the §4.2
42
+ * code-based route assembly gives packaged pages a router-agnostic way to
43
+ * read route state.
44
+ *
45
+ * WIDGETS: none today. No cross-domain notifications card is slot-mounted —
46
+ * deliveries shown on other domains' detail pages are those hosts' concern.
47
+ */
48
+ export function createNotificationsAdminExtension(options = {}) {
49
+ const { basePath = "/notifications", labels = {} } = options;
50
+ const { templates = "Templates", reminderRules = "Reminder Rules", deliveries = "Deliveries", reminderRuns = "Reminder Runs", preview = "Preview", settings = "Settings", } = labels;
51
+ return defineAdminExtension({
52
+ id: "notifications",
53
+ routes: [
54
+ {
55
+ id: "notifications-templates-index",
56
+ path: `${basePath}/templates`,
57
+ title: templates,
58
+ },
59
+ {
60
+ id: "notifications-templates-detail",
61
+ path: `${basePath}/templates/$id`,
62
+ title: templates,
63
+ },
64
+ {
65
+ id: "notifications-reminder-rules-index",
66
+ path: `${basePath}/reminder-rules`,
67
+ title: reminderRules,
68
+ },
69
+ {
70
+ id: "notifications-reminder-rules-detail",
71
+ path: `${basePath}/reminder-rules/$id`,
72
+ title: reminderRules,
73
+ },
74
+ {
75
+ id: "notifications-deliveries",
76
+ path: `${basePath}/deliveries`,
77
+ title: deliveries,
78
+ },
79
+ {
80
+ id: "notifications-reminder-runs",
81
+ path: `${basePath}/reminder-runs`,
82
+ title: reminderRuns,
83
+ },
84
+ {
85
+ id: "notifications-preview",
86
+ path: `${basePath}/preview`,
87
+ title: preview,
88
+ },
89
+ {
90
+ id: "notifications-settings",
91
+ path: `${basePath}/settings`,
92
+ title: settings,
93
+ },
94
+ ],
95
+ });
96
+ }
@@ -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"}