@voyantjs/ui 0.20.0 → 0.21.1
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/components/contract-template-authoring-help.d.ts +11 -2
- package/dist/components/contract-template-authoring-help.d.ts.map +1 -1
- package/dist/components/contract-template-authoring-help.js +46 -5
- package/dist/components/country-combobox.d.ts.map +1 -1
- package/dist/components/country-combobox.js +9 -4
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -1
- package/dist/components/notification-deliveries-page.d.ts.map +1 -1
- package/dist/components/notification-deliveries-page.js +70 -7
- package/dist/components/notification-reminder-rule-dialog.d.ts.map +1 -1
- package/dist/components/notification-reminder-rule-dialog.js +33 -27
- package/dist/components/notification-reminder-rules-page.d.ts.map +1 -1
- package/dist/components/notification-reminder-rules-page.js +23 -2
- package/dist/components/notification-template-dialog.d.ts +1 -1
- package/dist/components/notification-template-dialog.d.ts.map +1 -1
- package/dist/components/notification-template-dialog.js +106 -36
- package/dist/components/rich-text-variable-extension.d.ts.map +1 -1
- package/dist/components/rich-text-variable-extension.js +9 -5
- package/package.json +5 -5
|
@@ -21,7 +21,15 @@ export type TemplateAuthoringSnippet = {
|
|
|
21
21
|
type ContractTemplateAuthoringHelpProps = {
|
|
22
22
|
variableGroups: TemplateAuthoringVariableGroup[];
|
|
23
23
|
snippets?: TemplateAuthoringSnippet[];
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated Variables now expose a copy-to-clipboard button instead of
|
|
26
|
+
* an inline insert. Kept for prop-shape compatibility with older callers.
|
|
27
|
+
*/
|
|
24
28
|
onInsertVariable?: (variable: TemplateAuthoringVariable) => void;
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated Snippets now expose a copy-to-clipboard button instead of
|
|
31
|
+
* an inline insert. Kept for prop-shape compatibility with older callers.
|
|
32
|
+
*/
|
|
25
33
|
onInsertSnippet?: (snippet: TemplateAuthoringSnippet) => void;
|
|
26
34
|
className?: string;
|
|
27
35
|
title?: string;
|
|
@@ -34,11 +42,12 @@ type ContractTemplateAuthoringHelpProps = {
|
|
|
34
42
|
searchPlaceholder?: string;
|
|
35
43
|
noVariables?: string;
|
|
36
44
|
example?: string;
|
|
37
|
-
|
|
45
|
+
copy?: string;
|
|
46
|
+
copied?: string;
|
|
38
47
|
liquidUsage?: string;
|
|
39
48
|
noLiquidSnippets?: string;
|
|
40
49
|
};
|
|
41
50
|
};
|
|
42
|
-
export declare function ContractTemplateAuthoringHelp({ variableGroups, snippets, onInsertVariable, onInsertSnippet, className, title, description, messages, }: ContractTemplateAuthoringHelpProps): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
export declare function ContractTemplateAuthoringHelp({ variableGroups, snippets, onInsertVariable: _deprecatedOnInsertVariable, onInsertSnippet: _deprecatedOnInsertSnippet, className, title, description, messages, }: ContractTemplateAuthoringHelpProps): import("react/jsx-runtime").JSX.Element;
|
|
43
52
|
export {};
|
|
44
53
|
//# sourceMappingURL=contract-template-authoring-help.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-template-authoring-help.d.ts","sourceRoot":"","sources":["../../src/components/contract-template-authoring-help.tsx"],"names":[],"mappings":"AAYA,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,yBAAyB,EAAE,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,kCAAkC,GAAG;IACxC,cAAc,EAAE,8BAA8B,EAAE,CAAA;IAChD,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAA;IACrC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,yBAAyB,KAAK,IAAI,CAAA;IAChE,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE;YACL,SAAS,CAAC,EAAE,MAAM,CAAA;YAClB,MAAM,CAAC,EAAE,MAAM,CAAA;SAChB,CAAA;QACD,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;CACF,CAAA;
|
|
1
|
+
{"version":3,"file":"contract-template-authoring-help.d.ts","sourceRoot":"","sources":["../../src/components/contract-template-authoring-help.tsx"],"names":[],"mappings":"AAYA,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,yBAAyB,EAAE,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,KAAK,kCAAkC,GAAG;IACxC,cAAc,EAAE,8BAA8B,EAAE,CAAA;IAChD,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAA;IACrC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,yBAAyB,KAAK,IAAI,CAAA;IAChE;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE;YACL,SAAS,CAAC,EAAE,MAAM,CAAA;YAClB,MAAM,CAAC,EAAE,MAAM,CAAA;SAChB,CAAA;QACD,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;CACF,CAAA;AA+CD,wBAAgB,6BAA6B,CAAC,EAC5C,cAAc,EACd,QAAa,EACb,gBAAgB,EAAE,2BAA2B,EAC7C,eAAe,EAAE,0BAA0B,EAC3C,SAAS,EACT,KAA4B,EAC5B,WAAwH,EACxH,QAAQ,GACT,EAAE,kCAAkC,2CAsMpC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { SearchIcon, SparklesIcon } from "lucide-react";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { CheckIcon, CopyIcon, SearchIcon, SparklesIcon } from "lucide-react";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
import { Badge } from "./badge";
|
|
@@ -8,12 +8,53 @@ import { Button } from "./button";
|
|
|
8
8
|
import { Input } from "./input";
|
|
9
9
|
import { ScrollArea } from "./scroll-area";
|
|
10
10
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
|
|
11
|
+
function useCopyToClipboard(timeoutMs = 1200) {
|
|
12
|
+
const [copiedId, setCopiedId] = React.useState(null);
|
|
13
|
+
const timeoutRef = React.useRef(null);
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
return () => {
|
|
16
|
+
if (timeoutRef.current)
|
|
17
|
+
clearTimeout(timeoutRef.current);
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
const copy = React.useCallback(async (id, text) => {
|
|
21
|
+
try {
|
|
22
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
23
|
+
await navigator.clipboard.writeText(text);
|
|
24
|
+
}
|
|
25
|
+
else if (typeof document !== "undefined") {
|
|
26
|
+
// Fallback for browsers without the async clipboard API.
|
|
27
|
+
const textarea = document.createElement("textarea");
|
|
28
|
+
textarea.value = text;
|
|
29
|
+
textarea.setAttribute("readonly", "");
|
|
30
|
+
textarea.style.position = "fixed";
|
|
31
|
+
textarea.style.opacity = "0";
|
|
32
|
+
document.body.appendChild(textarea);
|
|
33
|
+
textarea.select();
|
|
34
|
+
document.execCommand("copy");
|
|
35
|
+
document.body.removeChild(textarea);
|
|
36
|
+
}
|
|
37
|
+
setCopiedId(id);
|
|
38
|
+
if (timeoutRef.current)
|
|
39
|
+
clearTimeout(timeoutRef.current);
|
|
40
|
+
timeoutRef.current = setTimeout(() => setCopiedId(null), timeoutMs);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Silent — copy failures are surfaced by the lack of the
|
|
44
|
+
// confirmation state. Better than spamming a toast we don't own.
|
|
45
|
+
}
|
|
46
|
+
}, [timeoutMs]);
|
|
47
|
+
return { copiedId, copy };
|
|
48
|
+
}
|
|
11
49
|
function matchesSearch(haystack, query) {
|
|
12
50
|
return haystack.toLowerCase().includes(query);
|
|
13
51
|
}
|
|
14
|
-
export function ContractTemplateAuthoringHelp({ variableGroups, snippets = [], onInsertVariable, onInsertSnippet, className, title = "Template variables", description = "Templates render with Liquid. Use output tags for variables and control tags for loops and conditionals.", messages, }) {
|
|
52
|
+
export function ContractTemplateAuthoringHelp({ variableGroups, snippets = [], onInsertVariable: _deprecatedOnInsertVariable, onInsertSnippet: _deprecatedOnInsertSnippet, className, title = "Template variables", description = "Templates render with Liquid. Use output tags for variables and control tags for loops and conditionals.", messages, }) {
|
|
15
53
|
const [search, setSearch] = React.useState("");
|
|
16
54
|
const normalizedQuery = search.trim().toLowerCase();
|
|
55
|
+
const { copiedId, copy } = useCopyToClipboard();
|
|
56
|
+
const copyLabel = messages?.copy ?? "Copy";
|
|
57
|
+
const copiedLabel = messages?.copied ?? "Copied";
|
|
17
58
|
const filteredGroups = React.useMemo(() => {
|
|
18
59
|
if (!normalizedQuery) {
|
|
19
60
|
return variableGroups;
|
|
@@ -33,6 +74,6 @@ export function ContractTemplateAuthoringHelp({ variableGroups, snippets = [], o
|
|
|
33
74
|
}
|
|
34
75
|
return snippets.filter((snippet) => matchesSearch([snippet.label, snippet.description, snippet.code].filter(Boolean).join(" "), normalizedQuery));
|
|
35
76
|
}, [normalizedQuery, snippets]);
|
|
36
|
-
return (_jsxs("div", { className: cn("rounded-md border bg-muted/20", className), children: [_jsxs("div", { className: "flex flex-col gap-1 border-b px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(SparklesIcon, { className: "h-4 w-4 text-muted-foreground" }), _jsx("h3", { className: "text-sm font-medium", children: title })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: description })] }), _jsxs(Tabs, { defaultValue: "variables", className: "gap-3 p-4", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "variables", children: messages?.tabs?.variables ?? "Variables" }), _jsx(TabsTrigger, { value: "liquid", children: messages?.tabs?.liquid ?? "Liquid" })] }), _jsxs("div", { className: "relative", children: [_jsx(SearchIcon, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: messages?.searchPlaceholder ?? "Search variables or snippets...", className: "pl-9" })] }), _jsx(TabsContent, { value: "variables", children: _jsx(ScrollArea, { className: "h-80", children: _jsx("div", { className: "space-y-4 pr-3", children: filteredGroups.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: messages?.noVariables ?? "No variables match this search." })) : (filteredGroups.map((group) => (_jsxs("section", { className: "space-y-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("h4", { className: "text-sm font-medium", children: group.label }), _jsx(Badge, { variant: "outline", children: group.variables.length })] }), group.description ? (_jsx("p", { className: "text-xs text-muted-foreground", children: group.description })) : null] }), _jsx("div", { className: "space-y-2", children: group.variables.map((variable) => (_jsx("div", { className: "rounded-md border bg-background p-3 shadow-xs", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-medium", children: variable.label }), _jsx(Badge, { variant: "outline", className: "font-normal", children: variable.type })] }), _jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-xs", children: `{{ ${variable.key} }}` }), variable.description ? (_jsx("p", { className: "text-xs text-muted-foreground", children: variable.description })) : null, _jsxs("p", { className: "text-xs text-muted-foreground", children: [messages?.example ?? "Example", ":", " ", _jsx("span", { className: "font-mono text-foreground", children: variable.example })] })] }),
|
|
37
|
-
"Use {{ ... }} for output and {% ... %} for control flow." }), filteredSnippets.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: messages?.noLiquidSnippets ?? "No Liquid snippets match this search." })) : (filteredSnippets.map((snippet) => (_jsxs("div", { className: "rounded-md border bg-background p-3 shadow-xs", children: [_jsxs("div", { className: "mb-2 flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: snippet.label }), _jsx("p", { className: "text-xs text-muted-foreground", children: snippet.description })] }),
|
|
77
|
+
return (_jsxs("div", { className: cn("rounded-md border bg-muted/20", className), children: [_jsxs("div", { className: "flex flex-col gap-1 border-b px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(SparklesIcon, { className: "h-4 w-4 text-muted-foreground" }), _jsx("h3", { className: "text-sm font-medium", children: title })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: description })] }), _jsxs(Tabs, { defaultValue: "variables", className: "gap-3 p-4", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "variables", children: messages?.tabs?.variables ?? "Variables" }), _jsx(TabsTrigger, { value: "liquid", children: messages?.tabs?.liquid ?? "Liquid" })] }), _jsxs("div", { className: "relative", children: [_jsx(SearchIcon, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: messages?.searchPlaceholder ?? "Search variables or snippets...", className: "pl-9" })] }), _jsx(TabsContent, { value: "variables", children: _jsx(ScrollArea, { className: "h-80", children: _jsx("div", { className: "space-y-4 pr-3", children: filteredGroups.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: messages?.noVariables ?? "No variables match this search." })) : (filteredGroups.map((group) => (_jsxs("section", { className: "space-y-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("h4", { className: "text-sm font-medium", children: group.label }), _jsx(Badge, { variant: "outline", children: group.variables.length })] }), group.description ? (_jsx("p", { className: "text-xs text-muted-foreground", children: group.description })) : null] }), _jsx("div", { className: "space-y-2", children: group.variables.map((variable) => (_jsx("div", { className: "rounded-md border bg-background p-3 shadow-xs", children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-medium", children: variable.label }), _jsx(Badge, { variant: "outline", className: "font-normal", children: variable.type })] }), _jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-xs", children: `{{ ${variable.key} }}` }), variable.description ? (_jsx("p", { className: "text-xs text-muted-foreground", children: variable.description })) : null, _jsxs("p", { className: "text-xs text-muted-foreground", children: [messages?.example ?? "Example", ":", " ", _jsx("span", { className: "font-mono text-foreground", children: variable.example })] })] }), _jsx(Button, { type: "button", size: "sm", variant: "outline", "aria-label": `${copyLabel} ${variable.key}`, onClick: () => copy(`var:${variable.key}`, `{{ ${variable.key} }}`), children: copiedId === `var:${variable.key}` ? (_jsxs(_Fragment, { children: [_jsx(CheckIcon, { className: "mr-1.5 h-3.5 w-3.5" }), copiedLabel] })) : (_jsxs(_Fragment, { children: [_jsx(CopyIcon, { className: "mr-1.5 h-3.5 w-3.5" }), copyLabel] })) })] }) }, variable.key))) })] }, group.id)))) }) }) }), _jsx(TabsContent, { value: "liquid", children: _jsx(ScrollArea, { className: "h-80", children: _jsxs("div", { className: "space-y-3 pr-3", children: [_jsx("div", { className: "rounded-md border bg-background p-3 text-xs text-muted-foreground", children: messages?.liquidUsage ??
|
|
78
|
+
"Use {{ ... }} for output and {% ... %} for control flow." }), filteredSnippets.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: messages?.noLiquidSnippets ?? "No Liquid snippets match this search." })) : (filteredSnippets.map((snippet) => (_jsxs("div", { className: "rounded-md border bg-background p-3 shadow-xs", children: [_jsxs("div", { className: "mb-2 flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: snippet.label }), _jsx("p", { className: "text-xs text-muted-foreground", children: snippet.description })] }), _jsx(Button, { type: "button", size: "sm", variant: "outline", "aria-label": `${copyLabel} ${snippet.label}`, onClick: () => copy(`snippet:${snippet.id}`, snippet.code), children: copiedId === `snippet:${snippet.id}` ? (_jsxs(_Fragment, { children: [_jsx(CheckIcon, { className: "mr-1.5 h-3.5 w-3.5" }), copiedLabel] })) : (_jsxs(_Fragment, { children: [_jsx(CopyIcon, { className: "mr-1.5 h-3.5 w-3.5" }), copyLabel] })) })] }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: snippet.code })] }, snippet.id))))] }) }) })] })] }));
|
|
38
79
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"country-combobox.d.ts","sourceRoot":"","sources":["../../src/components/country-combobox.tsx"],"names":[],"mappings":"AAqBA,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,WAAiC,EACjC,SAAiC,EACjC,QAAQ,GACT,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"country-combobox.d.ts","sourceRoot":"","sources":["../../src/components/country-combobox.tsx"],"names":[],"mappings":"AAqBA,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,WAAiC,EACjC,SAAiC,EACjC,QAAQ,GACT,EAAE,oBAAoB,2CAsEtB"}
|
|
@@ -21,10 +21,15 @@ export function CountryCombobox({ value, onChange, placeholder = "Search countri
|
|
|
21
21
|
setInputValue(selectedLabel);
|
|
22
22
|
}, [selectedLabel]);
|
|
23
23
|
const itemCodes = React.useMemo(() => COUNTRY_LIST.map((c) => c.code), []);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// base-ui filters and displays via `itemToStringLabel`; the
|
|
25
|
+
// `itemToStringValue` prop is for form submission only. We want
|
|
26
|
+
// searches like "rom" to match Romania, so the label string includes
|
|
27
|
+
// the country name.
|
|
28
|
+
const itemToStringLabel = React.useCallback((code) => {
|
|
29
|
+
const match = COUNTRY_BY_CODE.get(code);
|
|
30
|
+
return match ? `${match.name} (${match.code})` : code;
|
|
31
|
+
}, []);
|
|
32
|
+
return (_jsxs(Combobox, { items: itemCodes, value: normalized, inputValue: inputValue, autoHighlight: true, disabled: disabled, itemToStringLabel: itemToStringLabel, itemToStringValue: (code) => code, onInputValueChange: (next) => {
|
|
28
33
|
setInputValue(next);
|
|
29
34
|
if (!next)
|
|
30
35
|
onChange(null);
|
|
@@ -21,6 +21,7 @@ import { NotificationTemplateAuthoringHelp } from "./notification-template-autho
|
|
|
21
21
|
import { NotificationTemplateDetailPage } from "./notification-template-detail-page";
|
|
22
22
|
import { NotificationTemplatesPage } from "./notification-templates-page";
|
|
23
23
|
import { OverviewMetric } from "./overview-metric";
|
|
24
|
+
import { RadioGroup, RadioGroupItem } from "./radio-group";
|
|
24
25
|
import { RichTextEditor } from "./rich-text-editor";
|
|
25
26
|
import { ScrollArea, ScrollBar } from "./scroll-area";
|
|
26
27
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
@@ -82,5 +83,5 @@ declare const SidebarMenuSubButton: {
|
|
|
82
83
|
({ asChild, children, ...props }: Record<string, unknown> & AsChildProps): import("react/jsx-runtime").JSX.Element;
|
|
83
84
|
displayName: string;
|
|
84
85
|
};
|
|
85
|
-
export { Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, ConfirmActionButton, ContractTemplateAuthoringHelp, cn, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, NotificationDeliveriesPage, NotificationDeliveryDetailDialog, NotificationReminderRulesPage, NotificationReminderRunsPage, NotificationTemplateAuthoringHelp, NotificationTemplateDetailPage, NotificationTemplatesPage, OverviewMetric, RichTextEditor, ScrollArea, ScrollBar, Select, SelectContent, SelectItem, SelectionActionBar, SelectTrigger, SelectValue, Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarTrigger, Switch, Textarea, Toaster, useSidebar, };
|
|
86
|
+
export { Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, ConfirmActionButton, ContractTemplateAuthoringHelp, cn, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, NotificationDeliveriesPage, NotificationDeliveryDetailDialog, NotificationReminderRulesPage, NotificationReminderRunsPage, NotificationTemplateAuthoringHelp, NotificationTemplateDetailPage, NotificationTemplatesPage, OverviewMetric, RadioGroup, RadioGroupItem, RichTextEditor, ScrollArea, ScrollBar, Select, SelectContent, SelectItem, SelectionActionBar, SelectTrigger, SelectValue, Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarTrigger, Switch, Textarea, Toaster, useSidebar, };
|
|
86
87
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.tsx"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.tsx"],"names":[],"mappings":"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"}
|
package/dist/components/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { NotificationTemplateAuthoringHelp } from "./notification-template-autho
|
|
|
22
22
|
import { NotificationTemplateDetailPage } from "./notification-template-detail-page";
|
|
23
23
|
import { NotificationTemplatesPage } from "./notification-templates-page";
|
|
24
24
|
import { OverviewMetric } from "./overview-metric";
|
|
25
|
+
import { RadioGroup, RadioGroupItem } from "./radio-group";
|
|
25
26
|
import { RichTextEditor } from "./rich-text-editor";
|
|
26
27
|
import { ScrollArea, ScrollBar } from "./scroll-area";
|
|
27
28
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
@@ -82,4 +83,4 @@ const DropdownMenuTrigger = withAsChild(LocalDropdownMenuTrigger, "DropdownMenuT
|
|
|
82
83
|
const DropdownMenuItem = withAsChild(LocalDropdownMenuItem, "DropdownMenuItem");
|
|
83
84
|
const SidebarMenuButton = withAsChild(LocalSidebarMenuButton, "SidebarMenuButton");
|
|
84
85
|
const SidebarMenuSubButton = withAsChild(LocalSidebarMenuSubButton, "SidebarMenuSubButton");
|
|
85
|
-
export { Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, ConfirmActionButton, ContractTemplateAuthoringHelp, cn, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, NotificationDeliveriesPage, NotificationDeliveryDetailDialog, NotificationReminderRulesPage, NotificationReminderRunsPage, NotificationTemplateAuthoringHelp, NotificationTemplateDetailPage, NotificationTemplatesPage, OverviewMetric, RichTextEditor, ScrollArea, ScrollBar, Select, SelectContent, SelectItem, SelectionActionBar, SelectTrigger, SelectValue, Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarTrigger, Switch, Textarea, Toaster, useSidebar, };
|
|
86
|
+
export { Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, ConfirmActionButton, ContractTemplateAuthoringHelp, cn, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label, NotificationDeliveriesPage, NotificationDeliveryDetailDialog, NotificationReminderRulesPage, NotificationReminderRunsPage, NotificationTemplateAuthoringHelp, NotificationTemplateDetailPage, NotificationTemplatesPage, OverviewMetric, RadioGroup, RadioGroupItem, RichTextEditor, ScrollArea, ScrollBar, Select, SelectContent, SelectItem, SelectionActionBar, SelectTrigger, SelectValue, Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarTrigger, Switch, Textarea, Toaster, useSidebar, };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-deliveries-page.d.ts","sourceRoot":"","sources":["../../src/components/notification-deliveries-page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"notification-deliveries-page.d.ts","sourceRoot":"","sources":["../../src/components/notification-deliveries-page.tsx"],"names":[],"mappings":"AAuBA,wBAAgB,0BAA0B,4CAiLzC"}
|
|
@@ -1,22 +1,85 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useNotificationDeliveries, } from "@voyantjs/notifications-react";
|
|
4
|
-
import { Loader2 } from "lucide-react";
|
|
3
|
+
import { useNotificationDeliveries, useNotificationDeliveryMutation, } from "@voyantjs/notifications-react";
|
|
4
|
+
import { Loader2, RotateCcw, Search } from "lucide-react";
|
|
5
5
|
import { useState } from "react";
|
|
6
6
|
import { Badge } from "./badge";
|
|
7
|
+
import { Button } from "./button";
|
|
8
|
+
import { Dialog, DialogBody, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "./index";
|
|
7
9
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
8
10
|
export function NotificationDeliveriesPage() {
|
|
9
11
|
const [channel, setChannel] = useState("all");
|
|
10
12
|
const [status, setStatus] = useState("all");
|
|
13
|
+
const [selectedDelivery, setSelectedDelivery] = useState(null);
|
|
14
|
+
const deliveryMutation = useNotificationDeliveryMutation();
|
|
15
|
+
const selectedDeliveryId = selectedDelivery?.id;
|
|
11
16
|
const { data, isPending } = useNotificationDeliveries({
|
|
12
17
|
channel: channel === "all" ? undefined : channel,
|
|
13
18
|
status: status === "all" ? undefined : status,
|
|
14
19
|
limit: 50,
|
|
15
20
|
offset: 0,
|
|
16
21
|
});
|
|
17
|
-
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: "Deliveries" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Review notification delivery attempts, rendered payloads, and provider-level outcomes." })] }), _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: "Channel" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All channels" }), _jsx(SelectItem, { value: "email", children: "Email" }), _jsx(SelectItem, { value: "sms", children: "SMS" })] })] }), _jsxs(Select, { value: status, onValueChange: (value) => setStatus(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[140px]", children: _jsx(SelectValue, { placeholder: "Status" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All statuses" }), _jsx(SelectItem, { value: "pending", children: "Pending" }), _jsx(SelectItem, { value: "sent", children: "Sent" }), _jsx(SelectItem, { value: "failed", children: "Failed" }), _jsx(SelectItem, { value: "cancelled", children: "Cancelled" })] })] })] }), 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: "No deliveries yet." }) })) : 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("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 ?? "direct" }), _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 }),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
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: "Deliveries" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Review notification delivery attempts, rendered payloads, and provider-level outcomes." })] }), _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: "Channel" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All channels" }), _jsx(SelectItem, { value: "email", children: "Email" }), _jsx(SelectItem, { value: "sms", children: "SMS" })] })] }), _jsxs(Select, { value: status, onValueChange: (value) => setStatus(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[140px]", children: _jsx(SelectValue, { placeholder: "Status" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All statuses" }), _jsx(SelectItem, { value: "pending", children: "Pending" }), _jsx(SelectItem, { value: "sent", children: "Sent" }), _jsx(SelectItem, { value: "failed", children: "Failed" }), _jsx(SelectItem, { value: "cancelled", children: "Cancelled" })] })] })] }), 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: "No deliveries yet." }) })) : 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 ?? "direct" }), _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"
|
|
23
|
+
? "default"
|
|
24
|
+
: delivery.status === "failed"
|
|
25
|
+
? "destructive"
|
|
26
|
+
: "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: () => {
|
|
27
|
+
deliveryMutation.resend.mutate(delivery.id, {
|
|
28
|
+
onError(error) {
|
|
29
|
+
window.alert(error instanceof Error
|
|
30
|
+
? error.message
|
|
31
|
+
: "Notification resend failed");
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}, children: [deliveryMutation.resend.isPending ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(RotateCcw, { className: "mr-2 h-4 w-4" })), "Resend"] })) : null, _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setSelectedDelivery(delivery), children: [_jsx(Search, { className: "mr-2 h-4 w-4" }), "Details"] })] }) })] }, delivery.id))) })] }) })) : null, _jsx(DeliveryDetailsDialog, { delivery: selectedDelivery, open: Boolean(selectedDelivery), onOpenChange: (open) => {
|
|
35
|
+
if (!open)
|
|
36
|
+
setSelectedDelivery(null);
|
|
37
|
+
}, onResend: selectedDelivery?.status === "failed" && selectedDeliveryId
|
|
38
|
+
? () => {
|
|
39
|
+
deliveryMutation.resend.mutate(selectedDeliveryId, {
|
|
40
|
+
onError(error) {
|
|
41
|
+
window.alert(error instanceof Error ? error.message : "Notification resend failed");
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
: undefined, isResending: deliveryMutation.resend.isPending })] }));
|
|
46
|
+
}
|
|
47
|
+
function DeliveryDetailsDialog({ delivery, open, onOpenChange, onResend, isResending = false, }) {
|
|
48
|
+
if (!delivery)
|
|
49
|
+
return null;
|
|
50
|
+
const failureLog = readRecord(delivery.metadata?.failureLog);
|
|
51
|
+
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: "Delivery details" }), _jsx(DialogDescription, { children: "Provider response, failure log, rendered content, and payload for this notification." })] }), 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" })), "Resend"] })) : null] }) }), _jsxs(DialogBody, { className: "space-y-5", children: [_jsxs("section", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx(Detail, { label: "Delivery ID", value: delivery.id, mono: true }), _jsx(Detail, { label: "Status", value: delivery.status }), _jsx(Detail, { label: "Provider", value: delivery.provider }), _jsx(Detail, { label: "Provider message ID", value: delivery.providerMessageId ?? "—", mono: true }), _jsx(Detail, { label: "Template", value: delivery.templateSlug ?? "direct", mono: true }), _jsx(Detail, { label: "Channel", value: delivery.channel }), _jsx(Detail, { label: "Created", value: formatDateTime(delivery.createdAt) }), _jsx(Detail, { label: "Failed", value: formatDateTime(delivery.failedAt) }), _jsx(Detail, { label: "Sent", value: formatDateTime(delivery.sentAt) }), _jsx(Detail, { label: "Scheduled", value: formatDateTime(delivery.scheduledFor) })] }), delivery.errorMessage ? (_jsx(LogSection, { title: "Error message", tone: "destructive", children: delivery.errorMessage })) : null, failureLog ? (_jsx(JsonSection, { title: "Failure log", value: failureLog })) : delivery.status === "failed" ? (_jsx(LogSection, { title: "Failure log", children: "No structured failure log was captured." })) : null, _jsxs("section", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx(Detail, { label: "To", value: delivery.toAddress }), _jsx(Detail, { label: "From", value: delivery.fromAddress ?? "—" }), _jsx(Detail, { label: "Subject", value: delivery.subject ?? "—" }), _jsx(Detail, { label: "Target", value: formatTarget(delivery), mono: true })] }), _jsx(JsonSection, { title: "Payload data", value: delivery.payloadData }), _jsx(JsonSection, { title: "Metadata", value: delivery.metadata }), _jsx(BodySection, { title: "Text body", value: delivery.textBody }), _jsx(BodySection, { title: "HTML body", value: delivery.htmlBody })] })] }) }));
|
|
52
|
+
}
|
|
53
|
+
function Detail({ label, value, mono = false }) {
|
|
54
|
+
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 || "—" })] }));
|
|
55
|
+
}
|
|
56
|
+
function LogSection({ title, children, tone, }) {
|
|
57
|
+
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 ${tone === "destructive" ? "border-destructive/30 bg-destructive/10" : "bg-muted/30"}`, children: children })] }));
|
|
58
|
+
}
|
|
59
|
+
function JsonSection({ title, value }) {
|
|
60
|
+
if (!value)
|
|
61
|
+
return null;
|
|
62
|
+
return _jsx(LogSection, { title: title, children: JSON.stringify(value, null, 2) });
|
|
63
|
+
}
|
|
64
|
+
function BodySection({ title, value }) {
|
|
65
|
+
if (!value)
|
|
66
|
+
return null;
|
|
67
|
+
return _jsx(LogSection, { title: title, children: value });
|
|
68
|
+
}
|
|
69
|
+
function readRecord(value) {
|
|
70
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
71
|
+
? value
|
|
72
|
+
: null;
|
|
73
|
+
}
|
|
74
|
+
function formatDateTime(value) {
|
|
75
|
+
return value ? new Date(value).toLocaleString() : "—";
|
|
76
|
+
}
|
|
77
|
+
function formatTarget(delivery) {
|
|
78
|
+
const targetId = delivery.bookingId ??
|
|
79
|
+
delivery.invoiceId ??
|
|
80
|
+
delivery.paymentSessionId ??
|
|
81
|
+
delivery.personId ??
|
|
82
|
+
delivery.organizationId ??
|
|
83
|
+
delivery.targetId;
|
|
84
|
+
return targetId ? `${delivery.targetType}:${targetId}` : delivery.targetType;
|
|
22
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-reminder-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/notification-reminder-rule-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,8BAA8B,EAGpC,MAAM,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"notification-reminder-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/notification-reminder-rule-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,8BAA8B,EAGpC,MAAM,+BAA+B,CAAA;AAgDtC,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,2CAiNrC"}
|
|
@@ -13,16 +13,32 @@ import { Label } from "./label";
|
|
|
13
13
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
14
14
|
const reminderRuleFormSchema = z.object({
|
|
15
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
16
|
status: z.enum(["draft", "active", "archived"]).default("draft"),
|
|
21
|
-
targetType: z.enum([
|
|
17
|
+
targetType: z.enum([
|
|
18
|
+
"booking_confirmed",
|
|
19
|
+
"booking_payment_schedule",
|
|
20
|
+
"payment_complete",
|
|
21
|
+
"booking_cancelled_non_payment",
|
|
22
|
+
]),
|
|
22
23
|
channel: z.enum(["email", "sms"]),
|
|
23
24
|
templateId: z.string().min(1, "Template is required"),
|
|
24
25
|
relativeDaysFromDueDate: z.number().int().min(-365).max(365),
|
|
25
26
|
});
|
|
27
|
+
const reminderTargetOptions = [
|
|
28
|
+
{ value: "booking_confirmed", label: "Booking confirmed" },
|
|
29
|
+
{ value: "payment_complete", label: "Payment complete" },
|
|
30
|
+
{ value: "booking_cancelled_non_payment", label: "Booking cancelled (non-payment)" },
|
|
31
|
+
{ value: "booking_payment_schedule", label: "Booking payment schedule" },
|
|
32
|
+
];
|
|
33
|
+
const dueDateTargetTypes = new Set(["booking_payment_schedule"]);
|
|
34
|
+
function slugifyReminderRule(value) {
|
|
35
|
+
const slug = value
|
|
36
|
+
.trim()
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
39
|
+
.replace(/^-+|-+$/g, "");
|
|
40
|
+
return slug || "notification-rule";
|
|
41
|
+
}
|
|
26
42
|
export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuccess, }) {
|
|
27
43
|
const isEditing = Boolean(rule);
|
|
28
44
|
const { create, update } = useNotificationReminderRuleMutation();
|
|
@@ -30,7 +46,6 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
30
46
|
resolver: zodResolver(reminderRuleFormSchema),
|
|
31
47
|
defaultValues: {
|
|
32
48
|
name: "",
|
|
33
|
-
slug: "",
|
|
34
49
|
status: "draft",
|
|
35
50
|
targetType: "booking_payment_schedule",
|
|
36
51
|
channel: "email",
|
|
@@ -39,6 +54,8 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
39
54
|
},
|
|
40
55
|
});
|
|
41
56
|
const channel = form.watch("channel");
|
|
57
|
+
const targetType = form.watch("targetType");
|
|
58
|
+
const usesDueDateTiming = dueDateTargetTypes.has(targetType);
|
|
42
59
|
const { data: templates } = useNotificationTemplates({
|
|
43
60
|
channel,
|
|
44
61
|
status: "active",
|
|
@@ -54,9 +71,8 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
54
71
|
: "");
|
|
55
72
|
form.reset({
|
|
56
73
|
name: rule.name,
|
|
57
|
-
slug: rule.slug,
|
|
58
74
|
status: rule.status,
|
|
59
|
-
targetType: rule.targetType,
|
|
75
|
+
targetType: rule.targetType === "invoice" ? "booking_payment_schedule" : rule.targetType,
|
|
60
76
|
channel: rule.channel,
|
|
61
77
|
templateId: resolvedTemplateId,
|
|
62
78
|
relativeDaysFromDueDate: rule.relativeDaysFromDueDate,
|
|
@@ -70,14 +86,17 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
70
86
|
const onSubmit = async (values) => {
|
|
71
87
|
const payload = {
|
|
72
88
|
name: values.name,
|
|
73
|
-
slug:
|
|
89
|
+
slug: rule?.slug ??
|
|
90
|
+
`${slugifyReminderRule(values.targetType)}-${slugifyReminderRule(values.name)}`,
|
|
74
91
|
status: values.status,
|
|
75
92
|
targetType: values.targetType,
|
|
76
93
|
channel: values.channel,
|
|
77
94
|
provider: null,
|
|
78
95
|
templateId: values.templateId,
|
|
79
96
|
templateSlug: null,
|
|
80
|
-
relativeDaysFromDueDate: values.
|
|
97
|
+
relativeDaysFromDueDate: dueDateTargetTypes.has(values.targetType)
|
|
98
|
+
? values.relativeDaysFromDueDate
|
|
99
|
+
: 0,
|
|
81
100
|
isSystem: rule?.isSystem ?? false,
|
|
82
101
|
metadata: rule?.metadata ?? null,
|
|
83
102
|
};
|
|
@@ -90,32 +109,19 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
90
109
|
onSuccess();
|
|
91
110
|
};
|
|
92
111
|
const isPending = create.isPending || update.isPending;
|
|
93
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Reminder Rule" : "New Reminder Rule" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "
|
|
94
|
-
{ label: "Booking payment schedule", value: "booking_payment_schedule" },
|
|
95
|
-
{ label: "Invoice", value: "invoice" },
|
|
96
|
-
], value: form.watch("targetType"), onValueChange: (value) => {
|
|
112
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Reminder Rule" : "New Reminder Rule" }) }), _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: "Name" }), _jsx(Input, { ...form.register("name"), placeholder: "Payment due in 3 days" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Target" }), _jsxs(Select, { value: form.watch("targetType"), onValueChange: (value) => {
|
|
97
113
|
if (!value)
|
|
98
114
|
return;
|
|
99
115
|
form.setValue("targetType", value);
|
|
100
|
-
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }),
|
|
101
|
-
{ label: "Draft", value: "draft" },
|
|
102
|
-
{ label: "Active", value: "active" },
|
|
103
|
-
{ label: "Archived", value: "archived" },
|
|
104
|
-
], value: form.watch("status"), onValueChange: (value) => {
|
|
116
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: reminderTargetOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Status" }), _jsxs(Select, { value: form.watch("status"), onValueChange: (value) => {
|
|
105
117
|
if (!value)
|
|
106
118
|
return;
|
|
107
119
|
form.setValue("status", value);
|
|
108
|
-
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "draft", children: "Draft" }), _jsx(SelectItem, { value: "active", children: "Active" }), _jsx(SelectItem, { value: "archived", children: "Archived" })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Channel" }), _jsxs(Select, {
|
|
109
|
-
{ label: "Email", value: "email" },
|
|
110
|
-
{ label: "SMS", value: "sms" },
|
|
111
|
-
], value: form.watch("channel"), onValueChange: (value) => {
|
|
120
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "draft", children: "Draft" }), _jsx(SelectItem, { value: "active", children: "Active" }), _jsx(SelectItem, { value: "archived", children: "Archived" })] })] })] })] }), _jsxs("div", { className: usesDueDateTiming ? "grid grid-cols-2 gap-4" : "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Channel" }), _jsxs(Select, { value: form.watch("channel"), onValueChange: (value) => {
|
|
112
121
|
if (!value)
|
|
113
122
|
return;
|
|
114
123
|
form.setValue("channel", value);
|
|
115
|
-
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "email", children: "Email" }), _jsx(SelectItem, { value: "sms", children: "SMS" })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "
|
|
116
|
-
label: `${template.name} (${template.slug})`,
|
|
117
|
-
value: template.id,
|
|
118
|
-
})), value: form.watch("templateId"), onValueChange: (value) => {
|
|
124
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "email", children: "Email" }), _jsx(SelectItem, { value: "sms", children: "SMS" })] })] })] }), usesDueDateTiming ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Send timing" }), _jsx(Input, { type: "number", value: form.watch("relativeDaysFromDueDate"), onChange: (event) => form.setValue("relativeDaysFromDueDate", Number.parseInt(event.target.value || "0", 10)) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Days from due date: -3 sends 3 days before, 0 on the due date, 3 after." })] })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Template" }), _jsxs(Select, { value: form.watch("templateId"), onValueChange: (value) => {
|
|
119
125
|
if (!value)
|
|
120
126
|
return;
|
|
121
127
|
form.setValue("templateId", value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-reminder-rules-page.d.ts","sourceRoot":"","sources":["../../src/components/notification-reminder-rules-page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"notification-reminder-rules-page.d.ts","sourceRoot":"","sources":["../../src/components/notification-reminder-rules-page.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,6BAA6B,4CA+J5C"}
|
|
@@ -8,6 +8,27 @@ import { Button } from "./button";
|
|
|
8
8
|
import { Input } from "./input";
|
|
9
9
|
import { NotificationReminderRuleDialog } from "./notification-reminder-rule-dialog";
|
|
10
10
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
11
|
+
const reminderTargetLabels = {
|
|
12
|
+
booking_confirmed: "Booking confirmed",
|
|
13
|
+
booking_payment_schedule: "Booking payment schedule",
|
|
14
|
+
payment_complete: "Payment complete",
|
|
15
|
+
booking_cancelled_non_payment: "Booking cancelled (non-payment)",
|
|
16
|
+
};
|
|
17
|
+
const dueDateTargetTypes = new Set([
|
|
18
|
+
"booking_payment_schedule",
|
|
19
|
+
]);
|
|
20
|
+
function getReminderTargetLabel(targetType) {
|
|
21
|
+
if (targetType === "invoice")
|
|
22
|
+
return "Invoice";
|
|
23
|
+
return reminderTargetLabels[targetType] ?? targetType;
|
|
24
|
+
}
|
|
25
|
+
function formatReminderTiming(targetType, days) {
|
|
26
|
+
if (!dueDateTargetTypes.has(targetType))
|
|
27
|
+
return "Event";
|
|
28
|
+
if (days === 0)
|
|
29
|
+
return "Due date";
|
|
30
|
+
return days < 0 ? `${Math.abs(days)} days before` : `${days} days after`;
|
|
31
|
+
}
|
|
11
32
|
export function NotificationReminderRulesPage() {
|
|
12
33
|
const [search, setSearch] = useState("");
|
|
13
34
|
const [channel, setChannel] = useState("all");
|
|
@@ -21,10 +42,10 @@ export function NotificationReminderRulesPage() {
|
|
|
21
42
|
status: status === "all" ? undefined : status,
|
|
22
43
|
targetType: targetType === "all" ? undefined : targetType,
|
|
23
44
|
});
|
|
24
|
-
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Reminder Rules" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Schedule
|
|
45
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Reminder Rules" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Schedule booking payment reminders and event notifications against templates and channels." })] }), _jsxs(Button, { onClick: () => {
|
|
25
46
|
setEditing(undefined);
|
|
26
47
|
setDialogOpen(true);
|
|
27
|
-
}, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "New Rule"] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "relative max-w-sm flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Search rules...", value: search, onChange: (event) => setSearch(event.target.value), className: "pl-9" })] }), _jsxs(Select, { value: targetType, onValueChange: (value) => setTargetType(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[190px]", children: _jsx(SelectValue, { placeholder: "Target" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All targets" }),
|
|
48
|
+
}, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "New Rule"] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "relative max-w-sm flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Search rules...", value: search, onChange: (event) => setSearch(event.target.value), className: "pl-9" })] }), _jsxs(Select, { value: targetType, onValueChange: (value) => setTargetType(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[190px]", children: _jsx(SelectValue, { placeholder: "Target" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All targets" }), Object.entries(reminderTargetLabels).map(([value, label]) => (_jsx(SelectItem, { value: value, children: label }, value)))] })] }), _jsxs(Select, { value: channel, onValueChange: (value) => setChannel(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[140px]", children: _jsx(SelectValue, { placeholder: "Channel" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All channels" }), _jsx(SelectItem, { value: "email", children: "Email" }), _jsx(SelectItem, { value: "sms", children: "SMS" })] })] }), _jsxs(Select, { value: status, onValueChange: (value) => setStatus(value ?? "all"), children: [_jsx(SelectTrigger, { className: "w-[140px]", children: _jsx(SelectValue, { placeholder: "Status" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "All statuses" }), _jsx(SelectItem, { value: "draft", children: "Draft" }), _jsx(SelectItem, { value: "active", children: "Active" }), _jsx(SelectItem, { value: "archived", children: "Archived" })] })] })] }), 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: "No reminder rules yet." }) })) : 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: "Rule" }), _jsx("th", { className: "px-4 py-3", children: "Target" }), _jsx("th", { className: "px-4 py-3", children: "Channel" }), _jsx("th", { className: "px-4 py-3", children: "Timing" }), _jsx("th", { className: "px-4 py-3", children: "Status" }), _jsx("th", { className: "px-4 py-3 text-right", children: "Actions" })] }) }), _jsx("tbody", { children: data.data.map((rule) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-4 py-3", children: _jsx("div", { className: "font-medium", children: rule.name }) }), _jsx("td", { className: "px-4 py-3", children: getReminderTargetLabel(rule.targetType) }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: "outline", children: rule.channel }) }), _jsx("td", { className: "px-4 py-3", children: formatReminderTiming(rule.targetType, rule.relativeDaysFromDueDate) }), _jsx("td", { className: "px-4 py-3", children: _jsx(Badge, { variant: rule.status === "active" ? "default" : "secondary", children: rule.status }) }), _jsx("td", { className: "px-4 py-3 text-right", children: _jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
28
49
|
setEditing(rule);
|
|
29
50
|
setDialogOpen(true);
|
|
30
51
|
}, children: _jsx(Pencil, { className: "h-4 w-4" }) }) })] }, rule.id))) })] }) })) : null, _jsx(NotificationReminderRuleDialog, { open: dialogOpen, onOpenChange: setDialogOpen, rule: editing, onSuccess: () => {
|
|
@@ -5,6 +5,6 @@ type NotificationTemplateDialogProps = {
|
|
|
5
5
|
template?: NotificationTemplateRecord;
|
|
6
6
|
onSuccess: () => void;
|
|
7
7
|
};
|
|
8
|
-
export declare function NotificationTemplateDialog(
|
|
8
|
+
export declare function NotificationTemplateDialog(props: NotificationTemplateDialogProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
9
|
export {};
|
|
10
10
|
//# sourceMappingURL=notification-template-dialog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-template-dialog.d.ts","sourceRoot":"","sources":["../../src/components/notification-template-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,0BAA0B,EAIhC,MAAM,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"notification-template-dialog.d.ts","sourceRoot":"","sources":["../../src/components/notification-template-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,0BAA0B,EAIhC,MAAM,+BAA+B,CAAA;AA+DtC,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"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useNotificationTemplateAuthoring, useNotificationTemplateMutation, useNotificationTemplateTools, } from "@voyantjs/notifications-react";
|
|
4
4
|
import { Loader2 } from "lucide-react";
|
|
5
|
-
import { useEffect, useMemo, useState } from "react";
|
|
5
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { useForm } from "react-hook-form";
|
|
7
7
|
import { toast } from "sonner";
|
|
8
8
|
import { z } from "zod/v4";
|
|
9
9
|
import { zodResolver } from "../lib/zod-resolver";
|
|
10
10
|
import { Button } from "./button";
|
|
11
|
+
import { Checkbox } from "./checkbox";
|
|
11
12
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "./index";
|
|
12
13
|
import { Input } from "./input";
|
|
13
14
|
import { Label } from "./label";
|
|
@@ -15,7 +16,6 @@ import { NotificationTemplateAuthoringHelp } from "./notification-template-autho
|
|
|
15
16
|
import { RichTextEditor } from "./rich-text-editor";
|
|
16
17
|
import { insertPlainText, insertVariableToken } from "./rich-text-variable-extension";
|
|
17
18
|
import { ScrollArea } from "./scroll-area";
|
|
18
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
19
19
|
import { Switch } from "./switch";
|
|
20
20
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
|
|
21
21
|
import { Textarea } from "./textarea";
|
|
@@ -28,6 +28,13 @@ const STATUS_ITEMS = [
|
|
|
28
28
|
{ label: "Active", value: "active" },
|
|
29
29
|
{ label: "Archived", value: "archived" },
|
|
30
30
|
];
|
|
31
|
+
const ATTACHMENT_ITEMS = [
|
|
32
|
+
{ label: "Contract", value: "contract" },
|
|
33
|
+
{ label: "Invoice", value: "invoice" },
|
|
34
|
+
{ label: "Brochure", value: "brochure" },
|
|
35
|
+
];
|
|
36
|
+
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";
|
|
37
|
+
const templateAttachmentSchema = z.enum(["contract", "invoice", "brochure"]);
|
|
31
38
|
const templateFormSchema = z.object({
|
|
32
39
|
name: z.string().min(1, "Name is required"),
|
|
33
40
|
slug: z
|
|
@@ -40,6 +47,7 @@ const templateFormSchema = z.object({
|
|
|
40
47
|
htmlTemplate: z.string().optional(),
|
|
41
48
|
textTemplate: z.string().optional(),
|
|
42
49
|
fromAddress: z.string().optional(),
|
|
50
|
+
attachments: z.array(templateAttachmentSchema).default([]),
|
|
43
51
|
active: z.boolean(),
|
|
44
52
|
});
|
|
45
53
|
function parsePath(path) {
|
|
@@ -100,10 +108,44 @@ function appendTemplateValue(current, addition) {
|
|
|
100
108
|
function variableReference(key) {
|
|
101
109
|
return `{{ ${key} }}`;
|
|
102
110
|
}
|
|
103
|
-
|
|
111
|
+
function getMetadataRecord(value) {
|
|
112
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function readTemplateAttachments(metadata) {
|
|
118
|
+
const record = getMetadataRecord(metadata);
|
|
119
|
+
const value = record?.attachments;
|
|
120
|
+
if (!Array.isArray(value)) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
const allowed = new Set(ATTACHMENT_ITEMS.map((item) => item.value));
|
|
124
|
+
return ATTACHMENT_ITEMS.map((item) => item.value).filter((attachment) => allowed.has(attachment) && value.includes(attachment));
|
|
125
|
+
}
|
|
126
|
+
function buildTemplateMetadata(metadata, attachments) {
|
|
127
|
+
const current = getMetadataRecord(metadata);
|
|
128
|
+
const next = current ? { ...current } : {};
|
|
129
|
+
if (attachments.length > 0) {
|
|
130
|
+
next.attachments = [...attachments];
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
delete next.attachments;
|
|
134
|
+
}
|
|
135
|
+
return Object.keys(next).length > 0 ? next : null;
|
|
136
|
+
}
|
|
137
|
+
export function NotificationTemplateDialog(props) {
|
|
138
|
+
if (!props.open) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return _jsx(NotificationTemplateDialogInner, { ...props });
|
|
142
|
+
}
|
|
143
|
+
function NotificationTemplateDialogInner({ open, onOpenChange, template, onSuccess, }) {
|
|
104
144
|
const isEditing = Boolean(template);
|
|
105
145
|
const { create, update } = useNotificationTemplateMutation();
|
|
106
146
|
const { preview, testSend } = useNotificationTemplateTools();
|
|
147
|
+
const previewResetRef = useRef(preview.reset);
|
|
148
|
+
const testSendResetRef = useRef(testSend.reset);
|
|
107
149
|
const { variableCatalog, liquidSnippets } = useNotificationTemplateAuthoring();
|
|
108
150
|
const [editorInstance, setEditorInstance] = useState(null);
|
|
109
151
|
const [insertionTarget, setInsertionTarget] = useState("body");
|
|
@@ -128,10 +170,14 @@ export function NotificationTemplateDialog({ open, onOpenChange, template, onSuc
|
|
|
128
170
|
htmlTemplate: "",
|
|
129
171
|
textTemplate: "",
|
|
130
172
|
fromAddress: "",
|
|
173
|
+
attachments: [],
|
|
131
174
|
active: true,
|
|
132
175
|
},
|
|
133
176
|
});
|
|
134
177
|
const channel = form.watch("channel");
|
|
178
|
+
const attachments = form.watch("attachments") ?? [];
|
|
179
|
+
previewResetRef.current = preview.reset;
|
|
180
|
+
testSendResetRef.current = testSend.reset;
|
|
135
181
|
useEffect(() => {
|
|
136
182
|
if (open && template) {
|
|
137
183
|
form.reset({
|
|
@@ -143,6 +189,7 @@ export function NotificationTemplateDialog({ open, onOpenChange, template, onSuc
|
|
|
143
189
|
htmlTemplate: template.htmlTemplate ?? "",
|
|
144
190
|
textTemplate: template.textTemplate ?? "",
|
|
145
191
|
fromAddress: template.fromAddress ?? "",
|
|
192
|
+
attachments: template.channel === "email" ? readTemplateAttachments(template.metadata) : [],
|
|
146
193
|
active: template.status === "active",
|
|
147
194
|
});
|
|
148
195
|
return;
|
|
@@ -154,16 +201,28 @@ export function NotificationTemplateDialog({ open, onOpenChange, template, onSuc
|
|
|
154
201
|
useEffect(() => {
|
|
155
202
|
if (!open)
|
|
156
203
|
return;
|
|
157
|
-
setInsertionTarget((current) =>
|
|
204
|
+
setInsertionTarget((current) => {
|
|
205
|
+
const next = channel === "sms" ? "text" : current === "text" ? "body" : current;
|
|
206
|
+
return next === current ? current : next;
|
|
207
|
+
});
|
|
158
208
|
}, [channel, open]);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!open || channel === "email" || (form.getValues("attachments") ?? []).length === 0)
|
|
211
|
+
return;
|
|
212
|
+
form.setValue("attachments", [], {
|
|
213
|
+
shouldDirty: true,
|
|
214
|
+
shouldTouch: true,
|
|
215
|
+
shouldValidate: true,
|
|
216
|
+
});
|
|
217
|
+
}, [channel, form, open]);
|
|
159
218
|
useEffect(() => {
|
|
160
219
|
if (!open)
|
|
161
220
|
return;
|
|
162
221
|
setPreviewDataInput(defaultPreviewData);
|
|
163
222
|
setTestRecipient("");
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}, [defaultPreviewData, open
|
|
223
|
+
previewResetRef.current();
|
|
224
|
+
testSendResetRef.current();
|
|
225
|
+
}, [defaultPreviewData, open]);
|
|
167
226
|
const onSubmit = async (values) => {
|
|
168
227
|
const payload = {
|
|
169
228
|
name: values.name,
|
|
@@ -173,10 +232,12 @@ export function NotificationTemplateDialog({ open, onOpenChange, template, onSuc
|
|
|
173
232
|
status: values.active ? (values.status === "archived" ? "active" : values.status) : "draft",
|
|
174
233
|
subjectTemplate: values.channel === "email" ? values.subjectTemplate || null : null,
|
|
175
234
|
htmlTemplate: values.channel === "email" ? values.htmlTemplate || null : null,
|
|
176
|
-
textTemplate: values.textTemplate || null,
|
|
235
|
+
textTemplate: values.channel === "sms" ? values.textTemplate || null : null,
|
|
177
236
|
fromAddress: values.channel === "email" ? values.fromAddress || null : null,
|
|
178
237
|
isSystem: template?.isSystem ?? false,
|
|
179
|
-
metadata:
|
|
238
|
+
metadata: values.channel === "email"
|
|
239
|
+
? buildTemplateMetadata(template?.metadata, values.attachments)
|
|
240
|
+
: buildTemplateMetadata(template?.metadata, []),
|
|
180
241
|
};
|
|
181
242
|
if (isEditing && template) {
|
|
182
243
|
await update.mutateAsync({ id: template.id, input: payload });
|
|
@@ -229,7 +290,7 @@ export function NotificationTemplateDialog({ open, onOpenChange, template, onSuc
|
|
|
229
290
|
fromAddress: channel === "email" ? form.getValues("fromAddress") || null : null,
|
|
230
291
|
subjectTemplate: channel === "email" ? form.getValues("subjectTemplate") || null : null,
|
|
231
292
|
htmlTemplate: channel === "email" ? form.getValues("htmlTemplate") || null : null,
|
|
232
|
-
textTemplate: form.getValues("textTemplate") || null,
|
|
293
|
+
textTemplate: channel === "sms" ? form.getValues("textTemplate") || null : null,
|
|
233
294
|
data,
|
|
234
295
|
});
|
|
235
296
|
}
|
|
@@ -251,7 +312,7 @@ export function NotificationTemplateDialog({ open, onOpenChange, template, onSuc
|
|
|
251
312
|
from: channel === "email" ? form.getValues("fromAddress") || null : null,
|
|
252
313
|
subject: channel === "email" ? form.getValues("subjectTemplate") || null : null,
|
|
253
314
|
html: channel === "email" ? form.getValues("htmlTemplate") || null : null,
|
|
254
|
-
text: form.getValues("textTemplate") || null,
|
|
315
|
+
text: channel === "sms" ? form.getValues("textTemplate") || null : null,
|
|
255
316
|
data,
|
|
256
317
|
targetType: "other",
|
|
257
318
|
});
|
|
@@ -261,36 +322,45 @@ export function NotificationTemplateDialog({ open, onOpenChange, template, onSuc
|
|
|
261
322
|
toast.error(error instanceof Error ? error.message : "Test send failed");
|
|
262
323
|
}
|
|
263
324
|
};
|
|
264
|
-
|
|
265
|
-
|
|
325
|
+
const setAttachmentSelected = (attachment, checked) => {
|
|
326
|
+
const current = form.getValues("attachments") ?? [];
|
|
327
|
+
const next = checked
|
|
328
|
+
? [...current, attachment].filter((value, index, values) => values.indexOf(value) === index)
|
|
329
|
+
: current.filter((value) => value !== attachment);
|
|
330
|
+
form.setValue("attachments", next, {
|
|
331
|
+
shouldDirty: true,
|
|
332
|
+
shouldTouch: true,
|
|
333
|
+
shouldValidate: true,
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsx(DialogContent, { size: "xl", className: "h-[calc(100vh-2rem)]", children: _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "grid min-h-0 flex-1 grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Notification Template" : "New Notification Template" }) }), _jsx(ScrollArea, { className: "min-h-0 flex-1", children: _jsxs("div", { className: "grid gap-4 py-4 pr-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Name" }), _jsx(Input, { ...form.register("name"), placeholder: "Booking confirmation" }), form.formState.errors.name ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.name.message })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Slug" }), _jsx(Input, { ...form.register("slug"), placeholder: "booking-confirmation" }), form.formState.errors.slug ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.slug.message })) : null] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Channel" }), _jsx("select", { className: nativeSelectClassName, value: form.watch("channel"), onChange: (event) => {
|
|
337
|
+
const nextChannel = event.target.value;
|
|
338
|
+
if (form.getValues("channel") === nextChannel)
|
|
266
339
|
return;
|
|
267
|
-
form.setValue("channel",
|
|
268
|
-
|
|
269
|
-
|
|
340
|
+
form.setValue("channel", nextChannel, {
|
|
341
|
+
shouldDirty: true,
|
|
342
|
+
shouldTouch: true,
|
|
343
|
+
shouldValidate: true,
|
|
344
|
+
});
|
|
345
|
+
}, children: CHANNEL_ITEMS.map((item) => (_jsx("option", { value: item.value, children: item.label }, item.value))) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Status" }), _jsx("select", { className: nativeSelectClassName, value: form.watch("status"), onChange: (event) => {
|
|
346
|
+
const nextStatus = event.target.value;
|
|
347
|
+
if (form.getValues("status") === nextStatus)
|
|
270
348
|
return;
|
|
271
|
-
form.setValue("status",
|
|
272
|
-
|
|
349
|
+
form.setValue("status", nextStatus, {
|
|
350
|
+
shouldDirty: true,
|
|
351
|
+
shouldTouch: true,
|
|
352
|
+
shouldValidate: true,
|
|
353
|
+
});
|
|
354
|
+
}, children: STATUS_ITEMS.map((item) => (_jsx("option", { value: item.value, children: item.label }, item.value))) })] })] }), channel === "email" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Attachments" }), _jsx("div", { className: "flex flex-wrap gap-3", children: ATTACHMENT_ITEMS.map((item) => (_jsxs("div", { className: "flex h-9 items-center gap-2 rounded-md border px-3 text-sm", children: [_jsx(Checkbox, { id: `notification-template-attachment-${item.value}`, checked: attachments.includes(item.value), onCheckedChange: (checked) => setAttachmentSelected(item.value, checked === true) }), _jsx(Label, { htmlFor: `notification-template-attachment-${item.value}`, className: "cursor-pointer text-sm font-normal", children: item.label })] }, item.value))) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "From address" }), _jsx(Input, { ...form.register("fromAddress"), placeholder: "reservations@example.com" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Subject" }), _jsx(Input, { ...form.register("subjectTemplate"), placeholder: "Your booking {{ booking.reference }}" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "HTML body" }), _jsx(RichTextEditor, { value: form.watch("htmlTemplate") ?? "", onChange: (value) => form.setValue("htmlTemplate", value, {
|
|
273
355
|
shouldDirty: true,
|
|
274
356
|
shouldTouch: true,
|
|
275
357
|
shouldValidate: true,
|
|
276
|
-
}), placeholder: "Compose the email body using Liquid variables...", enableVariables: true, onEditorReady: setEditorInstance })] })] })) : null, _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
...(channel === "email"
|
|
280
|
-
? [
|
|
281
|
-
{ label: "Subject", value: "subject" },
|
|
282
|
-
{ label: "HTML body", value: "body" },
|
|
283
|
-
]
|
|
284
|
-
: []),
|
|
285
|
-
{
|
|
286
|
-
label: channel === "sms" ? "SMS body" : "Plain-text fallback",
|
|
287
|
-
value: "text",
|
|
288
|
-
},
|
|
289
|
-
], value: insertionTarget, onValueChange: (value) => {
|
|
290
|
-
if (!value)
|
|
358
|
+
}), placeholder: "Compose the email body using Liquid variables...", enableVariables: true, onEditorReady: setEditorInstance })] })] })) : null, channel === "sms" ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "SMS body" }), _jsx(Textarea, { ...form.register("textTemplate"), placeholder: 'Hi {{ traveler.firstName | default: "traveler" }}, your booking is confirmed.', rows: 6, className: "font-mono text-xs" })] })) : null, _jsxs(Tabs, { defaultValue: "authoring", children: [_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "authoring", children: "Authoring" }), _jsx(TabsTrigger, { value: "preview", children: "Preview & Test" })] }), _jsxs(TabsContent, { value: "authoring", className: "mt-4 space-y-4", children: [_jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-[180px_1fr] sm:items-center", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { children: "Insert into" }), _jsxs("select", { className: nativeSelectClassName, value: insertionTarget, onChange: (event) => {
|
|
359
|
+
const nextTarget = event.target.value;
|
|
360
|
+
if (nextTarget === insertionTarget)
|
|
291
361
|
return;
|
|
292
|
-
setInsertionTarget(
|
|
293
|
-
}, children: [
|
|
362
|
+
setInsertionTarget(nextTarget);
|
|
363
|
+
}, children: [channel === "email" ? _jsx("option", { value: "subject", children: "Subject" }) : null, channel === "email" ? _jsx("option", { value: "body", children: "HTML body" }) : null, channel === "sms" ? _jsx("option", { value: "text", children: "SMS body" }) : null] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Variables insert as Liquid tags in text fields and as inline chips in the rich-text HTML body." })] }), _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: "Preview data (JSON)" }), _jsx(Textarea, { value: previewDataInput, onChange: (event) => setPreviewDataInput(event.target.value), rows: 14, className: "font-mono text-xs", placeholder: '{"booking":{"reference":"BKG-2026-00125"}}' }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Use sample JSON to preview Liquid rendering and send a safe test message." })] }), _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, "Refresh Preview"] }) }), _jsxs("div", { className: "space-y-3 rounded-md border p-4", children: [_jsx("div", { className: "text-sm font-medium", children: "Rendered preview" }), 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: "Subject" }), _jsx("div", { className: "rounded-md border bg-muted/20 px-3 py-2 text-sm", children: preview.data?.subject || "No subject rendered yet." })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "HTML body" }), _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",
|
|
294
364
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: Preview HTML is generated server-side for template preview.
|
|
295
|
-
dangerouslySetInnerHTML: { __html: preview.data.html } })) : (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: "No HTML content rendered yet." })) })] })
|
|
365
|
+
dangerouslySetInnerHTML: { __html: preview.data.html } })) : (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: "No HTML content rendered yet." })) })] })] })) : (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "SMS body" }), _jsx("pre", { className: "whitespace-pre-wrap rounded-md border bg-muted/20 px-3 py-3 text-xs", children: preview.data?.text || "No SMS content rendered yet." })] }))] })] }), _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: "Test send" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Sends the current unsaved content through the configured provider path." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: channel === "email" ? "Recipient email" : "Recipient phone" }), _jsx(Input, { value: testRecipient, onChange: (event) => setTestRecipient(event.target.value), placeholder: channel === "email" ? "qa@example.com" : "+40 721 111 222" })] }), _jsxs("div", { className: "space-y-1 text-xs text-muted-foreground", children: [_jsx("div", { children: "Provider is selected automatically by the app runtime." }), channel === "email" ? (_jsxs("div", { children: ["From: ", form.watch("fromAddress") || "Default sender"] })) : null] }), _jsxs(Button, { type: "button", className: "w-full", onClick: handleTestSend, disabled: testSend.isPending, children: [testSend.isPending ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, "Send Test ", channel === "email" ? "Email" : "SMS"] }), testSend.data ? (_jsxs("div", { className: "rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-xs text-emerald-200", children: ["Delivery queued with status ", _jsx("strong", { children: testSend.data.status }), testSend.data.provider ? ` via ${testSend.data.provider}` : "", "."] })) : null] })] }) })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { className: "cursor-pointer", children: "Mark template active after saving" })] })] }) }), _jsxs(DialogFooter, { className: "mt-0", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isPending, children: [isPending ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Create Template"] })] })] }) }) }));
|
|
296
366
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-variable-extension.d.ts","sourceRoot":"","sources":["../../src/components/rich-text-variable-extension.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAA8B,IAAI,EAAa,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"rich-text-variable-extension.d.ts","sourceRoot":"","sources":["../../src/components/rich-text-variable-extension.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAA8B,IAAI,EAAa,MAAM,cAAc,CAAA;AAU1E,eAAO,MAAM,gBAAgB,gBA8H3B,CAAA;AAEF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,WAS9D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAK3D"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { InputRule, mergeAttributes, Node, PasteRule } from "@tiptap/core";
|
|
2
2
|
const variableInputRegex = /(?:^|\s)(\{\{\s*([^}]+)\s*\}\})$/;
|
|
3
|
-
|
|
3
|
+
// Paste accepts `{{ ... }}` regardless of what precedes it. The
|
|
4
|
+
// live-typing rule above keeps requiring whitespace so we don't gobble
|
|
5
|
+
// braces mid-word, but pasted content commonly has the variable
|
|
6
|
+
// tucked between punctuation (`Document ({{ customer.document.type }}):`,
|
|
7
|
+
// `["{{ foo }}"]`, etc.) and we still want those tokenised.
|
|
8
|
+
const variablePasteRegex = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
4
9
|
export const RichTextVariable = Node.create({
|
|
5
10
|
name: "variable",
|
|
6
11
|
group: "inline",
|
|
@@ -88,11 +93,10 @@ export const RichTextVariable = Node.create({
|
|
|
88
93
|
new PasteRule({
|
|
89
94
|
find: variablePasteRegex,
|
|
90
95
|
handler: ({ state, range, match }) => {
|
|
91
|
-
if (!match[
|
|
96
|
+
if (!match[1])
|
|
92
97
|
return;
|
|
93
|
-
const variableContent = match[
|
|
94
|
-
|
|
95
|
-
state.tr.replaceWith(range.from + addedPosition, range.to, this.type.create({
|
|
98
|
+
const variableContent = match[1].trim();
|
|
99
|
+
state.tr.replaceWith(range.from, range.to, this.type.create({
|
|
96
100
|
content: variableContent,
|
|
97
101
|
}));
|
|
98
102
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"tw-animate-css": "^1.3.5",
|
|
36
36
|
"vaul": "^1.1.2",
|
|
37
37
|
"zod": "^4.3.6",
|
|
38
|
-
"@voyantjs/i18n": "0.
|
|
39
|
-
"@voyantjs/notifications": "0.
|
|
40
|
-
"@voyantjs/notifications-react": "0.
|
|
41
|
-
"@voyantjs/utils": "0.
|
|
38
|
+
"@voyantjs/i18n": "0.21.1",
|
|
39
|
+
"@voyantjs/notifications": "0.21.1",
|
|
40
|
+
"@voyantjs/notifications-react": "0.21.1",
|
|
41
|
+
"@voyantjs/utils": "0.21.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@tailwindcss/postcss": "^4.1.11",
|