@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.
- package/dist/admin/notification-delivery-detail-dialog.js +1 -1
- package/dist/admin/notification-template-attachments-field.d.ts +10 -0
- package/dist/admin/notification-template-attachments-field.d.ts.map +1 -0
- package/dist/admin/notification-template-attachments-field.js +6 -0
- package/dist/admin/notification-template-detail-host.js +2 -2
- package/dist/admin/notification-template-dialog-utils.d.ts +53 -0
- package/dist/admin/notification-template-dialog-utils.d.ts.map +1 -0
- package/dist/admin/notification-template-dialog-utils.js +112 -0
- package/dist/admin/notification-template-dialog.d.ts.map +1 -1
- package/dist/admin/notification-template-dialog.js +6 -117
- package/dist/admin/notification-template-rendered-preview.d.ts +14 -0
- package/dist/admin/notification-template-rendered-preview.d.ts.map +1 -0
- package/dist/admin/notification-template-rendered-preview.js +6 -0
- package/package.json +3 -3
|
@@ -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":"
|
|
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,
|
|
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
|
-
|
|
17
|
-
|
|
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: [
|
|
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] }) }),
|
|
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.
|
|
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.
|
|
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"
|