@voyantjs/notifications-react 0.111.6 → 0.111.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ export function NotificationDeliveryDetailDialog({ deliveryId, open, onOpenChang
16
16
  : delivery.status === "failed"
17
17
  ? "destructive"
18
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.
19
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Notification HTML body is stored template output rendered for preview. -- owner: notifications-react; existing suppression is intentional pending typed cleanup.
20
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
21
  }
22
22
  function Section({ title, children }) {
@@ -0,0 +1,10 @@
1
+ import type { NotificationsUiMessages } from "../i18n/index.js";
2
+ import { type TemplateAttachment } from "./notification-template-dialog-utils.js";
3
+ type NotificationTemplateAttachmentsFieldProps = {
4
+ attachments: ReadonlyArray<TemplateAttachment>;
5
+ onAttachmentChange: (attachment: TemplateAttachment, checked: boolean) => void;
6
+ t: NotificationsUiMessages["admin"]["templateDialog"];
7
+ };
8
+ export declare function NotificationTemplateAttachmentsField({ attachments, onAttachmentChange, t, }: NotificationTemplateAttachmentsFieldProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=notification-template-attachments-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-attachments-field.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-attachments-field.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAC/D,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,yCAAyC,CAAA;AAEhD,KAAK,yCAAyC,GAAG;IAC/C,WAAW,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;IAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9E,CAAC,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAA;CACtD,CAAA;AAED,wBAAgB,oCAAoC,CAAC,EACnD,WAAW,EACX,kBAAkB,EAClB,CAAC,GACF,EAAE,yCAAyC,2CAuB3C"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Checkbox, Label } from "@voyantjs/ui/components";
3
+ import { ATTACHMENT_VALUES, attachmentItemLabel, } from "./notification-template-dialog-utils.js";
4
+ export function NotificationTemplateAttachmentsField({ attachments, onAttachmentChange, t, }) {
5
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.attachmentsLabel }), _jsx("div", { className: "flex flex-wrap gap-3", children: ATTACHMENT_VALUES.map((value) => (_jsxs("div", { className: "flex h-9 items-center gap-2 rounded-md border px-3 text-sm", children: [_jsx(Checkbox, { id: `notification-template-attachment-${value}`, checked: attachments.includes(value), onCheckedChange: (checked) => onAttachmentChange(value, checked === true) }), _jsx(Label, { htmlFor: `notification-template-attachment-${value}`, className: "cursor-pointer text-sm font-normal", children: attachmentItemLabel(t, value) })] }, value))) })] }));
6
+ }
@@ -136,9 +136,9 @@ export function NotificationTemplateDetailHost({ id }) {
136
136
  }
137
137
  const renderedPreview = preview.data;
138
138
  return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(DestinationLink, { href: resolveHref("notificationTemplate.list", {}), onNavigate: () => navigateTo("notificationTemplate.list", {}), className: "inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), t.backToTemplates] }), _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: template.name }), _jsx("p", { className: "font-mono text-xs text-muted-foreground", children: template.slug })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: template.channel }), _jsx(Badge, { variant: template.status === "active" ? "default" : "secondary", children: template.status })] })] }), _jsxs(Button, { onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), t.editTemplate] })] }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4", children: [_jsx(MetaCard, { label: t.metaChannel, value: template.channel }), _jsx(MetaCard, { label: t.metaFrom, value: template.fromAddress ?? common.defaultSender }), _jsx(MetaCard, { label: t.metaUpdated, value: new Date(template.updatedAt).toLocaleString() })] }), _jsxs(Tabs, { defaultValue: "overview", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "overview", children: t.tabOverview }), _jsx(TabsTrigger, { value: "preview", children: t.tabPreview }), _jsx(TabsTrigger, { value: "deliveries", children: t.recentDeliveries })] }), _jsx(TabsContent, { value: "overview", className: "mt-4 space-y-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.messageStructureTitle }) }), _jsxs(CardContent, { className: "space-y-3 text-sm", children: [_jsx(KeyValue, { label: t.subjectLabel, value: template.subjectTemplate ?? "—" }), _jsx(KeyValue, { label: t.textFallbackLabel, value: template.textTemplate ?? "—" }), _jsx(KeyValue, { label: t.descriptionLabel, value: template.metadata ? JSON.stringify(template.metadata) : "—" })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.htmlBodyTitle }) }), _jsx(CardContent, { children: template.htmlTemplate ? (_jsx("div", { className: "prose prose-sm max-w-none rounded-md border bg-background px-4 py-4 dark:prose-invert",
139
- // biome-ignore lint/security/noDangerouslySetInnerHtml: Notification template HTML is rendered for preview.
139
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Notification template HTML is rendered for preview. -- owner: notifications-react; existing suppression is intentional pending typed cleanup.
140
140
  dangerouslySetInnerHTML: { __html: template.htmlTemplate } })) : (_jsx("div", { className: "rounded-md border bg-muted/20 px-4 py-3 text-sm text-muted-foreground", children: t.noHtmlConfigured })) })] })] }) }), _jsx(TabsContent, { value: "preview", className: "mt-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.sampleDataTitle }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsx(Label, { children: t.customJsonLabel }), _jsx(Textarea, { value: previewDataInput || defaultPreviewData, onChange: (event) => setPreviewDataInput(event.target.value), rows: 16, className: "font-mono text-xs" }), _jsxs(Button, { type: "button", variant: "outline", onClick: handlePreview, disabled: preview.isPending, children: [preview.isPending ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, t.renderPreview] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.renderedOutputTitle }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsx(KeyValue, { label: t.subjectLabel, value: renderedPreview?.subject ?? t.notRenderedYet }), template.channel === "email" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.htmlBodyTitle }), renderedPreview?.html ? (_jsx("div", { className: "prose prose-sm max-w-none rounded-md border bg-background px-4 py-4 dark:prose-invert",
141
- // biome-ignore lint/security/noDangerouslySetInnerHtml: Rendered preview HTML is generated server-side for preview.
141
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Rendered preview HTML is generated server-side for preview. -- owner: notifications-react; existing suppression is intentional pending typed cleanup.
142
142
  dangerouslySetInnerHTML: { __html: renderedPreview.html } })) : (_jsx("div", { className: "rounded-md border bg-muted/20 px-4 py-3 text-sm text-muted-foreground", children: t.noRenderedHtml }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.textFallbackLabel }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: renderedPreview?.text ?? t.noRenderedText })] })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.smsBodyLabel }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: renderedPreview?.text ?? t.noRenderedText })] }))] })] })] }) }), _jsx(TabsContent, { value: "deliveries", className: "mt-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: t.recentDeliveries }) }), _jsx(CardContent, { children: deliveries.isPending ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) })) : deliveries.data?.data && deliveries.data.data.length > 0 ? (_jsx("div", { className: "rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase tracking-wide text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-4 py-3", children: "Recipient" }), _jsx("th", { className: "px-4 py-3", children: "Provider" }), _jsx("th", { className: "px-4 py-3", children: "Status" }), _jsx("th", { className: "px-4 py-3", children: "Created" }), _jsx("th", { className: "px-4 py-3 text-right", children: "View" })] }) }), _jsx("tbody", { children: deliveries.data.data.map((delivery) => (_jsxs("tr", { className: "border-t", children: [_jsxs("td", { className: "px-4 py-3", children: [_jsx("div", { children: delivery.toAddress }), delivery.subject ? (_jsx("div", { className: "text-xs text-muted-foreground", children: delivery.subject })) : null] }), _jsx("td", { className: "px-4 py-3", children: delivery.provider }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: delivery.status === "sent"
143
143
  ? "default"
144
144
  : delivery.status === "failed"
@@ -0,0 +1,53 @@
1
+ import { z } from "zod/v4";
2
+ import type { NotificationsUiMessages } from "../i18n/index.js";
3
+ export declare const CHANNEL_VALUES: readonly ["email", "sms"];
4
+ export declare const STATUS_VALUES: readonly ["draft", "active", "archived"];
5
+ export declare const ATTACHMENT_VALUES: readonly ["contract", "invoice", "brochure"];
6
+ export declare const channelItemLabel: (t: NotificationsUiMessages["admin"]["common"], value: (typeof CHANNEL_VALUES)[number]) => string;
7
+ export declare const statusItemLabel: (t: NotificationsUiMessages["admin"]["common"], value: (typeof STATUS_VALUES)[number]) => string;
8
+ export declare const attachmentItemLabel: (t: NotificationsUiMessages["admin"]["templateDialog"], value: (typeof ATTACHMENT_VALUES)[number]) => string;
9
+ export declare const nativeSelectClassName = "h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30";
10
+ declare const templateAttachmentSchema: z.ZodEnum<{
11
+ invoice: "invoice";
12
+ contract: "contract";
13
+ brochure: "brochure";
14
+ }>;
15
+ export declare const templateFormSchema: z.ZodObject<{
16
+ name: z.ZodString;
17
+ slug: z.ZodString;
18
+ channel: z.ZodEnum<{
19
+ email: "email";
20
+ sms: "sms";
21
+ }>;
22
+ status: z.ZodDefault<z.ZodEnum<{
23
+ draft: "draft";
24
+ active: "active";
25
+ archived: "archived";
26
+ }>>;
27
+ subjectTemplate: z.ZodOptional<z.ZodString>;
28
+ htmlTemplate: z.ZodOptional<z.ZodString>;
29
+ textTemplate: z.ZodOptional<z.ZodString>;
30
+ fromAddress: z.ZodOptional<z.ZodString>;
31
+ attachments: z.ZodDefault<z.ZodArray<z.ZodEnum<{
32
+ invoice: "invoice";
33
+ contract: "contract";
34
+ brochure: "brochure";
35
+ }>>>;
36
+ active: z.ZodBoolean;
37
+ }, z.core.$strip>;
38
+ export type FormValues = z.input<typeof templateFormSchema>;
39
+ export type FormOutput = z.output<typeof templateFormSchema>;
40
+ export type TemplateAttachment = z.infer<typeof templateAttachmentSchema>;
41
+ export type InsertionTarget = "subject" | "body" | "text";
42
+ export declare function buildSamplePayload(variableGroups: Array<{
43
+ variables: Array<{
44
+ key: string;
45
+ example: string;
46
+ }>;
47
+ }>): Record<string, unknown>;
48
+ export declare function appendTemplateValue(current: string | undefined, addition: string): string;
49
+ export declare function variableReference(key: string): string;
50
+ export declare function readTemplateAttachments(metadata: unknown): TemplateAttachment[];
51
+ export declare function buildTemplateMetadata(metadata: unknown, attachments: ReadonlyArray<TemplateAttachment>): Record<string, unknown> | null;
52
+ export {};
53
+ //# sourceMappingURL=notification-template-dialog-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-dialog-utils.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-dialog-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAC1B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAE/D,eAAO,MAAM,cAAc,2BAA4B,CAAA;AACvD,eAAO,MAAM,aAAa,0CAA2C,CAAA;AACrE,eAAO,MAAM,iBAAiB,8CAA+C,CAAA;AAE7E,eAAO,MAAM,gBAAgB,GAC3B,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAC7C,OAAO,CAAC,OAAO,cAAc,EAAE,MAAM,CAAC,WACgB,CAAA;AAExD,eAAO,MAAM,eAAe,GAC1B,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAC7C,OAAO,CAAC,OAAO,aAAa,EAAE,MAAM,CAAC,WAC0D,CAAA;AAEjG,eAAO,MAAM,mBAAmB,GAC9B,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,EACrD,OAAO,CAAC,OAAO,iBAAiB,EAAE,MAAM,CAAC,WAMf,CAAA;AAE5B,eAAO,MAAM,qBAAqB,+QAC4O,CAAA;AAE9Q,QAAA,MAAM,wBAAwB;;;;EAA8C,CAAA;AAE5E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;iBAc7B,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC5D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACzE,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAA;AA8CzD,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,KAAK,CAAC;IACpB,SAAS,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD,CAAC,2BASH;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,UAGhF;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,UAE5C;AASD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,GAAG,kBAAkB,EAAE,CAW/E;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,OAAO,EACjB,WAAW,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhC"}
@@ -0,0 +1,112 @@
1
+ import { z } from "zod/v4";
2
+ export const CHANNEL_VALUES = ["email", "sms"];
3
+ export const STATUS_VALUES = ["draft", "active", "archived"];
4
+ export const ATTACHMENT_VALUES = ["contract", "invoice", "brochure"];
5
+ export const channelItemLabel = (t, value) => (value === "email" ? t.channelEmail : t.channelSms);
6
+ export const statusItemLabel = (t, value) => (value === "draft" ? t.statusDraft : value === "active" ? t.statusActive : t.statusArchived);
7
+ export const attachmentItemLabel = (t, value) => value === "contract"
8
+ ? t.attachmentContract
9
+ : value === "invoice"
10
+ ? t.attachmentInvoice
11
+ : t.attachmentBrochure;
12
+ export const nativeSelectClassName = "h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30";
13
+ const templateAttachmentSchema = z.enum(["contract", "invoice", "brochure"]);
14
+ export const templateFormSchema = z.object({
15
+ name: z.string().min(1, "Name is required"),
16
+ slug: z
17
+ .string()
18
+ .min(1, "Slug is required")
19
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Must be kebab-case"),
20
+ channel: z.enum(["email", "sms"]),
21
+ status: z.enum(["draft", "active", "archived"]).default("draft"),
22
+ subjectTemplate: z.string().optional(),
23
+ htmlTemplate: z.string().optional(),
24
+ textTemplate: z.string().optional(),
25
+ fromAddress: z.string().optional(),
26
+ attachments: z.array(templateAttachmentSchema).default([]),
27
+ active: z.boolean(),
28
+ });
29
+ function parsePath(path) {
30
+ return path
31
+ .replace(/\[(\d+)\]/g, ".$1")
32
+ .split(".")
33
+ .filter(Boolean);
34
+ }
35
+ function setDeepValue(target, path, value) {
36
+ const segments = parsePath(path);
37
+ let current = target;
38
+ for (let index = 0; index < segments.length; index += 1) {
39
+ const segment = segments[index];
40
+ const isLast = index === segments.length - 1;
41
+ const nextSegment = segments[index + 1];
42
+ const nextIsIndex = nextSegment ? /^\d+$/.test(nextSegment) : false;
43
+ if (Array.isArray(current)) {
44
+ const arrayIndex = Number(segment);
45
+ if (Number.isNaN(arrayIndex))
46
+ return;
47
+ if (isLast) {
48
+ current[arrayIndex] = value;
49
+ return;
50
+ }
51
+ if (current[arrayIndex] == null) {
52
+ current[arrayIndex] = nextIsIndex ? [] : {};
53
+ }
54
+ current = current[arrayIndex];
55
+ continue;
56
+ }
57
+ if (typeof current !== "object" || current == null)
58
+ return;
59
+ const record = current;
60
+ if (isLast) {
61
+ record[segment] = value;
62
+ return;
63
+ }
64
+ if (record[segment] == null) {
65
+ record[segment] = nextIsIndex ? [] : {};
66
+ }
67
+ current = record[segment];
68
+ }
69
+ }
70
+ export function buildSamplePayload(variableGroups) {
71
+ const sample = {};
72
+ for (const group of variableGroups) {
73
+ for (const variable of group.variables) {
74
+ setDeepValue(sample, variable.key, variable.example);
75
+ }
76
+ }
77
+ return sample;
78
+ }
79
+ export function appendTemplateValue(current, addition) {
80
+ if (!current?.trim())
81
+ return addition;
82
+ return `${current}${current.endsWith("\n") ? "" : "\n"}${addition}`;
83
+ }
84
+ export function variableReference(key) {
85
+ return `{{ ${key} }}`;
86
+ }
87
+ function getMetadataRecord(value) {
88
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
89
+ return null;
90
+ }
91
+ return value;
92
+ }
93
+ export function readTemplateAttachments(metadata) {
94
+ const record = getMetadataRecord(metadata);
95
+ const value = record?.attachments;
96
+ if (!Array.isArray(value)) {
97
+ return [];
98
+ }
99
+ const allowed = new Set(ATTACHMENT_VALUES);
100
+ return ATTACHMENT_VALUES.filter((attachment) => allowed.has(attachment) && value.includes(attachment));
101
+ }
102
+ export function buildTemplateMetadata(metadata, attachments) {
103
+ const current = getMetadataRecord(metadata);
104
+ const next = current ? { ...current } : {};
105
+ if (attachments.length > 0) {
106
+ next.attachments = [...attachments];
107
+ }
108
+ else {
109
+ delete next.attachments;
110
+ }
111
+ return Object.keys(next).length > 0 ? next : null;
112
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"notification-template-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-dialog.tsx"],"names":[],"mappings":"AAkCA,OAAO,EACL,KAAK,0BAA0B,EAIhC,MAAM,aAAa,CAAA;AAoDpB,KAAK,+BAA+B,GAAG;IACrC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,0BAA0B,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AA2GD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,+BAA+B,kDAMhF"}
1
+ {"version":3,"file":"notification-template-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-dialog.tsx"],"names":[],"mappings":"AAgCA,OAAO,EACL,KAAK,0BAA0B,EAIhC,MAAM,aAAa,CAAA;AAsBpB,KAAK,+BAA+B,GAAG;IACrC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,0BAA0B,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,+BAA+B,kDAMhF"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { formatMessage } from "@voyantjs/i18n";
4
- import { Button, Checkbox, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, ScrollArea, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, } from "@voyantjs/ui/components";
4
+ import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, ScrollArea, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, } from "@voyantjs/ui/components";
5
5
  import { RichTextEditor } from "@voyantjs/ui/components/rich-text-editor";
6
6
  import { insertPlainText, insertVariableToken, } from "@voyantjs/ui/components/rich-text-variable-extension";
7
7
  import { zodResolver } from "@voyantjs/ui/lib/zod-resolver";
@@ -9,121 +9,12 @@ import { Loader2 } from "lucide-react";
9
9
  import { useEffect, useMemo, useRef, useState } from "react";
10
10
  import { useForm } from "react-hook-form";
11
11
  import { toast } from "sonner";
12
- import { z } from "zod/v4";
13
12
  import { useNotificationsUiMessagesOrDefault } from "../i18n/index.js";
14
13
  import { useNotificationTemplateAuthoring, useNotificationTemplateMutation, useNotificationTemplateTools, } from "../index.js";
14
+ import { NotificationTemplateAttachmentsField } from "./notification-template-attachments-field.js";
15
15
  import { NotificationTemplateAuthoringHelp } from "./notification-template-authoring-help.js";
16
- const CHANNEL_VALUES = ["email", "sms"];
17
- const STATUS_VALUES = ["draft", "active", "archived"];
18
- const ATTACHMENT_VALUES = ["contract", "invoice", "brochure"];
19
- const channelItemLabel = (t, value) => (value === "email" ? t.channelEmail : t.channelSms);
20
- const statusItemLabel = (t, value) => (value === "draft" ? t.statusDraft : value === "active" ? t.statusActive : t.statusArchived);
21
- const attachmentItemLabel = (t, value) => value === "contract"
22
- ? t.attachmentContract
23
- : value === "invoice"
24
- ? t.attachmentInvoice
25
- : t.attachmentBrochure;
26
- const nativeSelectClassName = "h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30";
27
- const templateAttachmentSchema = z.enum(["contract", "invoice", "brochure"]);
28
- const templateFormSchema = z.object({
29
- name: z.string().min(1, "Name is required"),
30
- slug: z
31
- .string()
32
- .min(1, "Slug is required")
33
- .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Must be kebab-case"),
34
- channel: z.enum(["email", "sms"]),
35
- status: z.enum(["draft", "active", "archived"]).default("draft"),
36
- subjectTemplate: z.string().optional(),
37
- htmlTemplate: z.string().optional(),
38
- textTemplate: z.string().optional(),
39
- fromAddress: z.string().optional(),
40
- attachments: z.array(templateAttachmentSchema).default([]),
41
- active: z.boolean(),
42
- });
43
- function parsePath(path) {
44
- return path
45
- .replace(/\[(\d+)\]/g, ".$1")
46
- .split(".")
47
- .filter(Boolean);
48
- }
49
- function setDeepValue(target, path, value) {
50
- const segments = parsePath(path);
51
- let current = target;
52
- for (let index = 0; index < segments.length; index += 1) {
53
- const segment = segments[index];
54
- const isLast = index === segments.length - 1;
55
- const nextSegment = segments[index + 1];
56
- const nextIsIndex = nextSegment ? /^\d+$/.test(nextSegment) : false;
57
- if (Array.isArray(current)) {
58
- const arrayIndex = Number(segment);
59
- if (Number.isNaN(arrayIndex))
60
- return;
61
- if (isLast) {
62
- current[arrayIndex] = value;
63
- return;
64
- }
65
- if (current[arrayIndex] == null) {
66
- current[arrayIndex] = nextIsIndex ? [] : {};
67
- }
68
- current = current[arrayIndex];
69
- continue;
70
- }
71
- if (typeof current !== "object" || current == null)
72
- return;
73
- const record = current;
74
- if (isLast) {
75
- record[segment] = value;
76
- return;
77
- }
78
- if (record[segment] == null) {
79
- record[segment] = nextIsIndex ? [] : {};
80
- }
81
- current = record[segment];
82
- }
83
- }
84
- function buildSamplePayload(variableGroups) {
85
- const sample = {};
86
- for (const group of variableGroups) {
87
- for (const variable of group.variables) {
88
- setDeepValue(sample, variable.key, variable.example);
89
- }
90
- }
91
- return sample;
92
- }
93
- function appendTemplateValue(current, addition) {
94
- if (!current?.trim())
95
- return addition;
96
- return `${current}${current.endsWith("\n") ? "" : "\n"}${addition}`;
97
- }
98
- function variableReference(key) {
99
- return `{{ ${key} }}`;
100
- }
101
- function getMetadataRecord(value) {
102
- if (!value || typeof value !== "object" || Array.isArray(value)) {
103
- return null;
104
- }
105
- return value;
106
- }
107
- function readTemplateAttachments(metadata) {
108
- const record = getMetadataRecord(metadata);
109
- const value = record?.attachments;
110
- if (!Array.isArray(value)) {
111
- return [];
112
- }
113
- const allowed = new Set(ATTACHMENT_VALUES);
114
- return ATTACHMENT_VALUES.filter((attachment) => allowed.has(attachment) && value.includes(attachment));
115
- }
116
- function buildTemplateMetadata(metadata, attachments) {
117
- const current = getMetadataRecord(metadata);
118
- const next = current ? { ...current } : {};
119
- if (attachments.length > 0) {
120
- next.attachments = [...attachments];
121
- }
122
- else {
123
- delete next.attachments;
124
- }
125
- return Object.keys(next).length > 0 ? next : null;
126
- }
16
+ import { appendTemplateValue, buildSamplePayload, buildTemplateMetadata, CHANNEL_VALUES, channelItemLabel, nativeSelectClassName, readTemplateAttachments, STATUS_VALUES, statusItemLabel, templateFormSchema, variableReference, } from "./notification-template-dialog-utils.js";
17
+ import { NotificationTemplateRenderedPreview } from "./notification-template-rendered-preview.js";
127
18
  export function NotificationTemplateDialog(props) {
128
19
  if (!props.open) {
129
20
  return null;
@@ -345,7 +236,7 @@ function NotificationTemplateDialogInner({ open, onOpenChange, template, onSucce
345
236
  shouldTouch: true,
346
237
  shouldValidate: true,
347
238
  });
348
- }, children: STATUS_VALUES.map((value) => (_jsx("option", { value: value, children: statusItemLabel(common, value) }, value))) })] })] }), channel === "email" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.attachmentsLabel }), _jsx("div", { className: "flex flex-wrap gap-3", children: ATTACHMENT_VALUES.map((value) => (_jsxs("div", { className: "flex h-9 items-center gap-2 rounded-md border px-3 text-sm", children: [_jsx(Checkbox, { id: `notification-template-attachment-${value}`, checked: attachments.includes(value), onCheckedChange: (checked) => setAttachmentSelected(value, checked === true) }), _jsx(Label, { htmlFor: `notification-template-attachment-${value}`, className: "cursor-pointer text-sm font-normal", children: attachmentItemLabel(t, value) })] }, value))) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.fromAddressLabel }), _jsx(Input, { ...form.register("fromAddress"), placeholder: t.fromAddressPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.subjectLabel }), _jsx(Input, { ...form.register("subjectTemplate"), placeholder: t.subjectPlaceholder })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.htmlBodyLabel }), _jsx(RichTextEditor, { value: form.watch("htmlTemplate") ?? "", onChange: (value) => form.setValue("htmlTemplate", value, {
239
+ }, children: STATUS_VALUES.map((value) => (_jsx("option", { value: value, children: statusItemLabel(common, value) }, value))) })] })] }), channel === "email" ? (_jsxs(_Fragment, { children: [_jsx(NotificationTemplateAttachmentsField, { attachments: attachments, onAttachmentChange: setAttachmentSelected, t: t }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.fromAddressLabel }), _jsx(Input, { ...form.register("fromAddress"), placeholder: t.fromAddressPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.subjectLabel }), _jsx(Input, { ...form.register("subjectTemplate"), placeholder: t.subjectPlaceholder })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.htmlBodyLabel }), _jsx(RichTextEditor, { value: form.watch("htmlTemplate") ?? "", onChange: (value) => form.setValue("htmlTemplate", value, {
349
240
  shouldDirty: true,
350
241
  shouldTouch: true,
351
242
  shouldValidate: true,
@@ -354,9 +245,7 @@ function NotificationTemplateDialogInner({ open, onOpenChange, template, onSucce
354
245
  if (nextTarget === insertionTarget)
355
246
  return;
356
247
  setInsertionTarget(nextTarget);
357
- }, children: [channel === "email" ? (_jsx("option", { value: "subject", children: t.insertTargetSubject })) : null, channel === "email" ? (_jsx("option", { value: "body", children: t.insertTargetHtmlBody })) : null, channel === "sms" ? (_jsx("option", { value: "text", children: t.insertTargetSmsBody })) : null] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.insertHint })] }), _jsx(NotificationTemplateAuthoringHelp, { variableGroups: variableGroups, snippets: liquidSnippets, onInsertVariable: (variable) => insertIntoTarget(variable.key, "variable"), onInsertSnippet: (snippet) => insertIntoTarget(snippet.code, "snippet") })] }), _jsx(TabsContent, { value: "preview", className: "mt-4 space-y-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_320px]", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.previewDataLabel }), _jsx(Textarea, { value: previewDataInput, onChange: (event) => setPreviewDataInput(event.target.value), rows: 14, className: "font-mono text-xs", placeholder: t.previewDataPlaceholder }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.previewDataHint })] }), _jsx("div", { className: "flex gap-2", children: _jsxs(Button, { type: "button", variant: "outline", onClick: handlePreview, disabled: preview.isPending, children: [preview.isPending ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, t.refreshPreview] }) }), _jsxs("div", { className: "space-y-3 rounded-md border p-4", children: [_jsx("div", { className: "text-sm font-medium", children: t.renderedPreviewTitle }), channel === "email" ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedSubjectLabel }), _jsx("div", { className: "rounded-md border bg-muted/20 px-3 py-2 text-sm", children: preview.data?.subject || t.noSubjectRendered })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedHtmlLabel }), _jsx("div", { className: "rounded-md border bg-background", children: preview.data?.html ? (_jsx("div", { className: "prose prose-sm max-w-none px-3 py-3 dark:prose-invert",
358
- // biome-ignore lint/security/noDangerouslySetInnerHtml: Preview HTML is generated server-side for template preview.
359
- dangerouslySetInnerHTML: { __html: preview.data.html } })) : (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: t.noHtmlRendered })) })] })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedSmsLabel }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: preview.data?.text || t.noSmsRendered })] }))] })] }), _jsxs("div", { className: "space-y-4 rounded-md border p-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-sm font-medium", children: t.testSendTitle }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.testSendDescription })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: channel === "email" ? t.recipientEmailLabel : t.recipientPhoneLabel }), _jsx(Input, { value: testRecipient, onChange: (event) => setTestRecipient(event.target.value), placeholder: channel === "email"
248
+ }, children: [channel === "email" ? (_jsx("option", { value: "subject", children: t.insertTargetSubject })) : null, channel === "email" ? (_jsx("option", { value: "body", children: t.insertTargetHtmlBody })) : null, channel === "sms" ? (_jsx("option", { value: "text", children: t.insertTargetSmsBody })) : null] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.insertHint })] }), _jsx(NotificationTemplateAuthoringHelp, { variableGroups: variableGroups, snippets: liquidSnippets, onInsertVariable: (variable) => insertIntoTarget(variable.key, "variable"), onInsertSnippet: (snippet) => insertIntoTarget(snippet.code, "snippet") })] }), _jsx(TabsContent, { value: "preview", className: "mt-4 space-y-4", children: _jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_320px]", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.previewDataLabel }), _jsx(Textarea, { value: previewDataInput, onChange: (event) => setPreviewDataInput(event.target.value), rows: 14, className: "font-mono text-xs", placeholder: t.previewDataPlaceholder }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.previewDataHint })] }), _jsx("div", { className: "flex gap-2", children: _jsxs(Button, { type: "button", variant: "outline", onClick: handlePreview, disabled: preview.isPending, children: [preview.isPending ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, t.refreshPreview] }) }), _jsx(NotificationTemplateRenderedPreview, { channel: channel, data: preview.data, t: t })] }), _jsxs("div", { className: "space-y-4 rounded-md border p-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-sm font-medium", children: t.testSendTitle }), _jsx("p", { className: "text-xs text-muted-foreground", children: t.testSendDescription })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: channel === "email" ? t.recipientEmailLabel : t.recipientPhoneLabel }), _jsx(Input, { value: testRecipient, onChange: (event) => setTestRecipient(event.target.value), placeholder: channel === "email"
360
249
  ? t.recipientEmailPlaceholder
361
250
  : t.recipientPhonePlaceholder })] }), _jsxs("div", { className: "space-y-1 text-xs text-muted-foreground", children: [_jsx("div", { children: t.providerAutoNote }), channel === "email" ? (_jsx("div", { children: formatMessage(t.fromNote, {
362
251
  sender: form.watch("fromAddress") || common.defaultSender,
@@ -0,0 +1,14 @@
1
+ import type { NotificationsUiMessages } from "../i18n/index.js";
2
+ type RenderedPreviewData = {
3
+ subject?: string | null;
4
+ html?: string | null;
5
+ text?: string | null;
6
+ };
7
+ type NotificationTemplateRenderedPreviewProps = {
8
+ channel: "email" | "sms";
9
+ data: RenderedPreviewData | null | undefined;
10
+ t: NotificationsUiMessages["admin"]["templateDialog"];
11
+ };
12
+ export declare function NotificationTemplateRenderedPreview({ channel, data, t, }: NotificationTemplateRenderedPreviewProps): import("react/jsx-runtime").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=notification-template-rendered-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-template-rendered-preview.d.ts","sourceRoot":"","sources":["../../src/admin/notification-template-rendered-preview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAE/D,KAAK,mBAAmB,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,CAAA;AAED,KAAK,wCAAwC,GAAG;IAC9C,OAAO,EAAE,OAAO,GAAG,KAAK,CAAA;IACxB,IAAI,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,CAAA;IAC5C,CAAC,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,CAAA;CACtD,CAAA;AAED,wBAAgB,mCAAmC,CAAC,EAClD,OAAO,EACP,IAAI,EACJ,CAAC,GACF,EAAE,wCAAwC,2CA6C1C"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function NotificationTemplateRenderedPreview({ channel, data, t, }) {
3
+ return (_jsxs("div", { className: "space-y-3 rounded-md border p-4", children: [_jsx("div", { className: "text-sm font-medium", children: t.renderedPreviewTitle }), channel === "email" ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedSubjectLabel }), _jsx("div", { className: "rounded-md border bg-muted/20 px-3 py-2 text-sm", children: data?.subject || t.noSubjectRendered })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedHtmlLabel }), _jsx("div", { className: "rounded-md border bg-background", children: data?.html ? (_jsx("div", { className: "prose prose-sm max-w-none px-3 py-3 dark:prose-invert",
4
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Preview HTML is generated server-side for template preview. -- owner: notifications-react; existing suppression is intentional pending typed cleanup.
5
+ dangerouslySetInnerHTML: { __html: data.html } })) : (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: t.noHtmlRendered })) })] })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t.renderedSmsLabel }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: data?.text || t.noSmsRendered })] }))] }));
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/notifications-react",
3
- "version": "0.111.6",
3
+ "version": "0.111.7",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -76,8 +76,8 @@
76
76
  "react-dom": "^19.0.0",
77
77
  "react-hook-form": "^7.60.0",
78
78
  "zod": "^4.0.0",
79
+ "@voyantjs/notifications": "^0.111.7",
79
80
  "@voyantjs/admin": "^0.111.0",
80
- "@voyantjs/notifications": "^0.111.6",
81
81
  "@voyantjs/ui": "^0.106.1"
82
82
  },
83
83
  "peerDependenciesMeta": {
@@ -114,7 +114,7 @@
114
114
  "zod": "^4.3.6",
115
115
  "@voyantjs/admin": "^0.111.0",
116
116
  "@voyantjs/i18n": "^0.106.0",
117
- "@voyantjs/notifications": "^0.111.6",
117
+ "@voyantjs/notifications": "^0.111.7",
118
118
  "@voyantjs/react": "^0.104.1",
119
119
  "@voyantjs/ui": "^0.106.1",
120
120
  "@voyantjs/voyant-typescript-config": "^0.1.0"