@voyantjs/ui 0.28.3 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/notification-reminder-rule-dialog.d.ts.map +1 -1
- package/dist/components/notification-reminder-rule-dialog.js +10 -17
- package/dist/components/notification-reminder-rules-page.d.ts +9 -1
- package/dist/components/notification-reminder-rules-page.d.ts.map +1 -1
- package/dist/components/notification-reminder-rules-page.js +7 -17
- package/package.json +5 -5
|
@@ -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;AAsDtC,KAAK,mCAAmC,GAAG;IACzC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,8BAA8B,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,8BAA8B,CAAC,EAC7C,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,SAAS,GACV,EAAE,mCAAmC,2CAkMrC"}
|
|
@@ -21,8 +21,9 @@ const reminderRuleFormSchema = z.object({
|
|
|
21
21
|
"booking_cancelled_non_payment",
|
|
22
22
|
]),
|
|
23
23
|
channel: z.enum(["email", "sms"]),
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// Optional default template — stages own per-channel templates and
|
|
25
|
+
// override this. Empty string is normalized to null in the payload.
|
|
26
|
+
templateId: z.string().optional(),
|
|
26
27
|
});
|
|
27
28
|
const reminderTargetOptions = [
|
|
28
29
|
{ value: "booking_confirmed", label: "Booking confirmed" },
|
|
@@ -30,7 +31,6 @@ const reminderTargetOptions = [
|
|
|
30
31
|
{ value: "booking_cancelled_non_payment", label: "Booking cancelled (non-payment)" },
|
|
31
32
|
{ value: "booking_payment_schedule", label: "Booking payment schedule" },
|
|
32
33
|
];
|
|
33
|
-
const dueDateTargetTypes = new Set(["booking_payment_schedule"]);
|
|
34
34
|
function slugifyReminderRule(value) {
|
|
35
35
|
const slug = value
|
|
36
36
|
.trim()
|
|
@@ -50,12 +50,9 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
50
50
|
targetType: "booking_payment_schedule",
|
|
51
51
|
channel: "email",
|
|
52
52
|
templateId: "",
|
|
53
|
-
relativeDaysFromDueDate: 0,
|
|
54
53
|
},
|
|
55
54
|
});
|
|
56
55
|
const channel = form.watch("channel");
|
|
57
|
-
const targetType = form.watch("targetType");
|
|
58
|
-
const usesDueDateTiming = dueDateTargetTypes.has(targetType);
|
|
59
56
|
const { data: templates } = useNotificationTemplates({
|
|
60
57
|
channel,
|
|
61
58
|
status: "active",
|
|
@@ -75,7 +72,6 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
75
72
|
targetType: rule.targetType === "invoice" ? "booking_payment_schedule" : rule.targetType,
|
|
76
73
|
channel: rule.channel,
|
|
77
74
|
templateId: resolvedTemplateId,
|
|
78
|
-
relativeDaysFromDueDate: rule.relativeDaysFromDueDate,
|
|
79
75
|
});
|
|
80
76
|
return;
|
|
81
77
|
}
|
|
@@ -92,11 +88,8 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
92
88
|
targetType: values.targetType,
|
|
93
89
|
channel: values.channel,
|
|
94
90
|
provider: null,
|
|
95
|
-
templateId: values.templateId,
|
|
91
|
+
templateId: values.templateId ? values.templateId : null,
|
|
96
92
|
templateSlug: null,
|
|
97
|
-
relativeDaysFromDueDate: dueDateTargetTypes.has(values.targetType)
|
|
98
|
-
? values.relativeDaysFromDueDate
|
|
99
|
-
: 0,
|
|
100
93
|
isSystem: rule?.isSystem ?? false,
|
|
101
94
|
metadata: rule?.metadata ?? null,
|
|
102
95
|
};
|
|
@@ -117,13 +110,13 @@ export function NotificationReminderRuleDialog({ open, onOpenChange, rule, onSuc
|
|
|
117
110
|
if (!value)
|
|
118
111
|
return;
|
|
119
112
|
form.setValue("status", 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" })] })] })] })] }),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
}, 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" })] })] })] })] }), _jsx("div", { className: "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) => {
|
|
114
|
+
if (!value)
|
|
115
|
+
return;
|
|
116
|
+
form.setValue("channel", value);
|
|
117
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "email", children: "Email" }), _jsx(SelectItem, { value: "sms", children: "SMS" })] })] })] }) }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Default template" }), _jsxs(Select, { value: form.watch("templateId"), onValueChange: (value) => {
|
|
125
118
|
if (!value)
|
|
126
119
|
return;
|
|
127
120
|
form.setValue("templateId", value);
|
|
128
|
-
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: "Select template" }) }), _jsx(SelectContent, { children: (templates?.data ?? []).map((template) => (_jsxs(SelectItem, { value: template.id, children: [template.name, " (", template.slug, ")"] }, template.id))) })] })] })] }), _jsxs(DialogFooter, { 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 Rule"] })] })] })] }) }));
|
|
121
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: "Select template" }) }), _jsx(SelectContent, { children: (templates?.data ?? []).map((template) => (_jsxs(SelectItem, { value: template.id, children: [template.name, " (", template.slug, ")"] }, template.id))) })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Used as a fallback. Per-stage channels can override this." })] }), !isEditing ? (_jsxs("p", { className: "rounded-md border border-dashed bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: ["After creating the rule, click ", _jsx("strong", { children: "Manage stages" }), " on the row to define when it fires (anchor, window, cadence) and which channels deliver it."] })) : null] }), _jsxs(DialogFooter, { 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 Rule"] })] })] })] }) }));
|
|
129
122
|
}
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface NotificationReminderRulesPageProps {
|
|
2
|
+
/**
|
|
3
|
+
* Path used for the per-rule "Manage stages" link. Receives the rule id and
|
|
4
|
+
* returns the URL the consumer's router understands. Defaults to
|
|
5
|
+
* `/notifications/reminder-rules/<id>`.
|
|
6
|
+
*/
|
|
7
|
+
manageStagesHref?: (ruleId: string) => string;
|
|
8
|
+
}
|
|
9
|
+
export declare function NotificationReminderRulesPage({ manageStagesHref, }?: NotificationReminderRulesPageProps): import("react/jsx-runtime").JSX.Element;
|
|
2
10
|
//# sourceMappingURL=notification-reminder-rules-page.d.ts.map
|
|
@@ -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":"AA4BA,MAAM,WAAW,kCAAkC;IACjD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAA;CAC9C;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,gBAAgE,GACjE,GAAE,kCAAuC,2CAoKzC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useNotificationReminderRules, } from "@voyantjs/notifications-react";
|
|
4
|
-
import { Loader2, Pencil, Plus, Search } from "lucide-react";
|
|
4
|
+
import { Layers, Loader2, Pencil, Plus, Search } from "lucide-react";
|
|
5
5
|
import { useState } from "react";
|
|
6
6
|
import { Badge } from "./badge.js";
|
|
7
|
-
import { Button } from "./button.js";
|
|
7
|
+
import { Button, buttonVariants } from "./button.js";
|
|
8
8
|
import { Input } from "./input.js";
|
|
9
9
|
import { NotificationReminderRuleDialog } from "./notification-reminder-rule-dialog.js";
|
|
10
10
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select.js";
|
|
@@ -14,22 +14,12 @@ const reminderTargetLabels = {
|
|
|
14
14
|
payment_complete: "Payment complete",
|
|
15
15
|
booking_cancelled_non_payment: "Booking cancelled (non-payment)",
|
|
16
16
|
};
|
|
17
|
-
const dueDateTargetTypes = new Set([
|
|
18
|
-
"booking_payment_schedule",
|
|
19
|
-
]);
|
|
20
17
|
function getReminderTargetLabel(targetType) {
|
|
21
18
|
if (targetType === "invoice")
|
|
22
19
|
return "Invoice";
|
|
23
20
|
return reminderTargetLabels[targetType] ?? targetType;
|
|
24
21
|
}
|
|
25
|
-
function
|
|
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
|
-
}
|
|
32
|
-
export function NotificationReminderRulesPage() {
|
|
22
|
+
export function NotificationReminderRulesPage({ manageStagesHref = (id) => `/notifications/reminder-rules/${id}`, } = {}) {
|
|
33
23
|
const [search, setSearch] = useState("");
|
|
34
24
|
const [channel, setChannel] = useState("all");
|
|
35
25
|
const [status, setStatus] = useState("all");
|
|
@@ -45,10 +35,10 @@ export function NotificationReminderRulesPage() {
|
|
|
45
35
|
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: () => {
|
|
46
36
|
setEditing(undefined);
|
|
47
37
|
setDialogOpen(true);
|
|
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: "
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
}, 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: "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: _jsx(Badge, { variant: rule.status === "active" ? "default" : "secondary", children: rule.status }) }), _jsx("td", { className: "px-4 py-3 text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsxs("a", { href: manageStagesHref(rule.id), className: buttonVariants({ variant: "ghost", size: "sm" }), children: [_jsx(Layers, { className: "mr-2 h-4 w-4" }), "Manage stages"] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
39
|
+
setEditing(rule);
|
|
40
|
+
setDialogOpen(true);
|
|
41
|
+
}, children: _jsx(Pencil, { className: "h-4 w-4" }) })] }) })] }, rule.id))) })] }) })) : null, _jsx(NotificationReminderRuleDialog, { open: dialogOpen, onOpenChange: setDialogOpen, rule: editing, onSuccess: () => {
|
|
52
42
|
setDialogOpen(false);
|
|
53
43
|
setEditing(undefined);
|
|
54
44
|
void refetch();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
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.30.0",
|
|
39
|
+
"@voyantjs/notifications": "0.30.0",
|
|
40
|
+
"@voyantjs/notifications-react": "0.30.0",
|
|
41
|
+
"@voyantjs/utils": "0.30.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@tailwindcss/postcss": "^4.1.11",
|