@fonderie/react-courier-admin-screens 0.9.0 → 0.11.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/index.cjs +362 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -21
- package/dist/index.d.ts +30 -21
- package/dist/index.js +372 -143
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createAdminT,
|
|
4
4
|
formatAdminDate,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
suggestTemplateLocales,
|
|
6
|
+
templateLanguages
|
|
7
7
|
} from "@fonderie/client";
|
|
8
8
|
import {
|
|
9
|
+
useBuiltInTemplate,
|
|
9
10
|
useTemplate,
|
|
11
|
+
useTemplateCatalog,
|
|
10
12
|
useTemplatePreview,
|
|
13
|
+
useTemplateResolution,
|
|
11
14
|
useTemplateRevisions,
|
|
12
15
|
useTemplates
|
|
13
16
|
} from "@fonderie/react-courier-admin";
|
|
@@ -28,8 +31,16 @@ function TemplateEditorScreen({
|
|
|
28
31
|
}) {
|
|
29
32
|
const t = useMemo(() => createAdminT(uiLocale), [uiLocale]);
|
|
30
33
|
const { template, isLoading, error, refresh } = useTemplate(client, type, locale);
|
|
31
|
-
const
|
|
32
|
-
const
|
|
34
|
+
const notSaved = error?.status === 404;
|
|
35
|
+
const { builtIn, isLoading: builtInLoading } = useBuiltInTemplate(client, type, locale);
|
|
36
|
+
const { catalog, refresh: refreshCatalog } = useTemplateCatalog(client);
|
|
37
|
+
const defaultLocale = catalog?.defaultLocale ?? "en-US";
|
|
38
|
+
const email = catalog?.emails.find((e) => e.type === type);
|
|
39
|
+
const languages = email ? templateLanguages(email, defaultLocale) : [];
|
|
40
|
+
const source = template ?? (notSaved ? builtIn : null);
|
|
41
|
+
const { resolution, resolve } = useTemplateResolution(client);
|
|
42
|
+
const [probe, setProbe] = useState("");
|
|
43
|
+
const { saveTemplate, removeTemplate } = useTemplates(client);
|
|
33
44
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
34
45
|
const { revisions, rollback } = useTemplateRevisions(client, type, locale);
|
|
35
46
|
const [isSaving, setIsSaving] = useState(false);
|
|
@@ -42,12 +53,15 @@ function TemplateEditorScreen({
|
|
|
42
53
|
const [sampleJson, setSampleJson] = useState("{}");
|
|
43
54
|
const [sampleError, setSampleError] = useState(null);
|
|
44
55
|
useEffect(() => {
|
|
45
|
-
if (!
|
|
46
|
-
setSubject(
|
|
47
|
-
setHtml(
|
|
48
|
-
setText(
|
|
49
|
-
setActive(template.active);
|
|
50
|
-
}, [template]);
|
|
56
|
+
if (!source) return;
|
|
57
|
+
setSubject(source.subject ?? "");
|
|
58
|
+
setHtml(source.html ?? "");
|
|
59
|
+
setText(source.text);
|
|
60
|
+
setActive(template ? template.active : true);
|
|
61
|
+
}, [source, template]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
void resolve(type, locale ?? defaultLocale);
|
|
64
|
+
}, [resolve, type, locale, defaultLocale]);
|
|
51
65
|
const run = useCallback(async () => {
|
|
52
66
|
let data = {};
|
|
53
67
|
try {
|
|
@@ -75,13 +89,15 @@ function TemplateEditorScreen({
|
|
|
75
89
|
}, [renderPreview, type, text, subject, html, locale, sampleJson, t]);
|
|
76
90
|
const runRef = useRef(run);
|
|
77
91
|
runRef.current = run;
|
|
78
|
-
const loadedType =
|
|
92
|
+
const loadedType = source ? `${type}:${locale ?? ""}` : null;
|
|
79
93
|
useEffect(() => {
|
|
80
94
|
if (!loadedType) return;
|
|
81
95
|
const timer = setTimeout(() => void runRef.current(), PREVIEW_DEBOUNCE_MS);
|
|
82
96
|
return () => clearTimeout(timer);
|
|
83
97
|
}, [loadedType, subject, html, text, sampleJson]);
|
|
84
|
-
const dirty = !!template && (subject !== (template.subject ?? "") || html !== (template.html ?? "") || text !== template.text || active !== template.active);
|
|
98
|
+
const dirty = notSaved && !!builtIn || !!template && (subject !== (template.subject ?? "") || html !== (template.html ?? "") || text !== template.text || active !== template.active);
|
|
99
|
+
const protectedRow = !locale && (email?.system ?? system);
|
|
100
|
+
const shipsHere = locale ? languages.some((l) => l.locale === locale && l.builtIn) : !!email?.builtIn.default;
|
|
85
101
|
const handleSubmit = async (event) => {
|
|
86
102
|
event.preventDefault();
|
|
87
103
|
setIsSaving(true);
|
|
@@ -92,7 +108,7 @@ function TemplateEditorScreen({
|
|
|
92
108
|
if (html) input.html = html;
|
|
93
109
|
if (template?.version !== void 0) input.ifVersion = template.version;
|
|
94
110
|
await saveTemplate(type, input, locale);
|
|
95
|
-
await refresh();
|
|
111
|
+
await Promise.all([refresh(), refreshCatalog()]);
|
|
96
112
|
onSaved?.();
|
|
97
113
|
} catch (err) {
|
|
98
114
|
setSaveError(err);
|
|
@@ -100,9 +116,32 @@ function TemplateEditorScreen({
|
|
|
100
116
|
setIsSaving(false);
|
|
101
117
|
}
|
|
102
118
|
};
|
|
103
|
-
const switchTo = (
|
|
104
|
-
if (dirty && !window.confirm(t("templates.editor.discardChanges")))
|
|
105
|
-
|
|
119
|
+
const switchTo = (next) => {
|
|
120
|
+
if (dirty && !(notSaved && !!builtIn) && !window.confirm(t("templates.editor.discardChanges")))
|
|
121
|
+
return;
|
|
122
|
+
onSelectLocale?.(next);
|
|
123
|
+
};
|
|
124
|
+
const handleReset = async () => {
|
|
125
|
+
if (!builtIn || !window.confirm(t("templates.editor.confirmReset", { locale: locale ?? defaultLocale })))
|
|
126
|
+
return;
|
|
127
|
+
setIsDeleting(true);
|
|
128
|
+
setSaveError(null);
|
|
129
|
+
try {
|
|
130
|
+
if (locale) {
|
|
131
|
+
await removeTemplate(type, locale);
|
|
132
|
+
} else {
|
|
133
|
+
const input = { text: builtIn.text, active };
|
|
134
|
+
if (builtIn.subject) input.subject = builtIn.subject;
|
|
135
|
+
if (builtIn.html) input.html = builtIn.html;
|
|
136
|
+
if (template?.version !== void 0) input.ifVersion = template.version;
|
|
137
|
+
await saveTemplate(type, input, null);
|
|
138
|
+
}
|
|
139
|
+
await Promise.all([refresh(), refreshCatalog()]);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
setSaveError(err);
|
|
142
|
+
} finally {
|
|
143
|
+
setIsDeleting(false);
|
|
144
|
+
}
|
|
106
145
|
};
|
|
107
146
|
const handleDelete = async () => {
|
|
108
147
|
const message = locale ? t("templates.editor.confirmDeleteLocale", { type, locale }) : t("templates.editor.confirmDelete", { type });
|
|
@@ -118,8 +157,9 @@ function TemplateEditorScreen({
|
|
|
118
157
|
setIsDeleting(false);
|
|
119
158
|
}
|
|
120
159
|
};
|
|
121
|
-
if (isLoading
|
|
122
|
-
|
|
160
|
+
if (isLoading || notSaved && builtInLoading)
|
|
161
|
+
return /* @__PURE__ */ jsx("p", { style: styles.status, children: t("templates.editor.loading") });
|
|
162
|
+
if (error && !(notSaved && builtIn))
|
|
123
163
|
return /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: error.explanation });
|
|
124
164
|
return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
|
|
125
165
|
/* @__PURE__ */ jsx("h1", { style: styles.title, children: type }),
|
|
@@ -127,19 +167,23 @@ function TemplateEditorScreen({
|
|
|
127
167
|
onSelectLocale ? (
|
|
128
168
|
// Each tab opens another stored row, so this is navigation, not an
|
|
129
169
|
// ARIA tablist: plain buttons, the open one marked aria-current.
|
|
130
|
-
/* @__PURE__ */ jsx("nav", { "aria-label": t("templates.editor.locales"), style: styles.tabs, children:
|
|
131
|
-
const selected =
|
|
170
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": t("templates.editor.locales"), style: styles.tabs, children: languages.map((l) => {
|
|
171
|
+
const selected = l.locale === (locale ?? null);
|
|
132
172
|
return /* @__PURE__ */ jsx(
|
|
133
173
|
"button",
|
|
134
174
|
{
|
|
135
175
|
type: "button",
|
|
136
176
|
"aria-current": selected ? "true" : void 0,
|
|
137
|
-
onClick: () => selected ? void 0 : switchTo(
|
|
138
|
-
style:
|
|
139
|
-
|
|
140
|
-
|
|
177
|
+
onClick: () => selected ? void 0 : switchTo({ type, locale: l.locale, system: !!email?.system }),
|
|
178
|
+
style: {
|
|
179
|
+
...selected ? styles.tabSelected : styles.tab,
|
|
180
|
+
...l.saved ? {} : styles.tabBuiltIn,
|
|
181
|
+
...l.active ? {} : styles.tabInactive
|
|
182
|
+
},
|
|
183
|
+
title: l.active ? void 0 : t("common.status.inactive"),
|
|
184
|
+
children: l.label
|
|
141
185
|
},
|
|
142
|
-
|
|
186
|
+
l.label
|
|
143
187
|
);
|
|
144
188
|
}) })
|
|
145
189
|
) : null,
|
|
@@ -150,17 +194,21 @@ function TemplateEditorScreen({
|
|
|
150
194
|
style: { ...styles.rollbackButton, marginLeft: "auto" },
|
|
151
195
|
onClick: () => {
|
|
152
196
|
if (dirty && !window.confirm(t("templates.editor.discardChanges"))) return;
|
|
153
|
-
onAddLocale(type, {
|
|
197
|
+
onAddLocale(type, {
|
|
198
|
+
locales: catalog ? suggestTemplateLocales(catalog, type) : [],
|
|
199
|
+
defaultLocale
|
|
200
|
+
});
|
|
154
201
|
},
|
|
155
202
|
children: t("templates.editor.addLocale")
|
|
156
203
|
}
|
|
157
204
|
) : null
|
|
158
205
|
] }) : null,
|
|
159
206
|
/* @__PURE__ */ jsxs("p", { style: styles.meta, children: [
|
|
160
|
-
/* @__PURE__ */ jsx("strong", { children: locale ??
|
|
161
|
-
|
|
162
|
-
template
|
|
163
|
-
|
|
207
|
+
/* @__PURE__ */ jsx("strong", { children: locale ?? defaultLocale }),
|
|
208
|
+
template ? ` \xB7 v${template.version}` : "",
|
|
209
|
+
template && protectedRow ? ` \xB7 ${t("templates.editor.builtInNote")}` : "",
|
|
210
|
+
!template && builtIn ? ` \xB7 ${t("templates.editor.builtInCopy")}` : "",
|
|
211
|
+
resolution && resolution.chain.length > 1 && resolution.requested === (locale ?? defaultLocale) ? ` \xB7 ${t("templates.editor.chain", { chain: [...resolution.chain, defaultLocale].join(" \u2192 ") })}` : ""
|
|
164
212
|
] }),
|
|
165
213
|
/* @__PURE__ */ jsxs("div", { style: styles.split, children: [
|
|
166
214
|
/* @__PURE__ */ jsxs("div", { style: styles.column, children: [
|
|
@@ -236,7 +284,16 @@ function TemplateEditorScreen({
|
|
|
236
284
|
}
|
|
237
285
|
),
|
|
238
286
|
!dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: t("templates.editor.noChanges") }),
|
|
239
|
-
|
|
287
|
+
template && shipsHere && builtIn ? /* @__PURE__ */ jsx(
|
|
288
|
+
"button",
|
|
289
|
+
{
|
|
290
|
+
type: "button",
|
|
291
|
+
disabled: isDeleting,
|
|
292
|
+
onClick: () => void handleReset(),
|
|
293
|
+
style: { ...styles.rollbackButton, height: 36, marginLeft: "auto" },
|
|
294
|
+
children: t("templates.editor.resetToBuiltIn")
|
|
295
|
+
}
|
|
296
|
+
) : template && !protectedRow ? /* @__PURE__ */ jsx(
|
|
240
297
|
"button",
|
|
241
298
|
{
|
|
242
299
|
type: "button",
|
|
@@ -291,6 +348,43 @@ function TemplateEditorScreen({
|
|
|
291
348
|
) : preview ? /* @__PURE__ */ jsx("pre", { style: styles.previewText, children: preview.text }) : /* @__PURE__ */ jsx("p", { style: styles.meta, children: t("templates.editor.nothingRendered") })
|
|
292
349
|
] })
|
|
293
350
|
] }),
|
|
351
|
+
/* @__PURE__ */ jsxs("div", { style: styles.revisions, children: [
|
|
352
|
+
/* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: t("templates.editor.whoReceives") }),
|
|
353
|
+
/* @__PURE__ */ jsxs(
|
|
354
|
+
"form",
|
|
355
|
+
{
|
|
356
|
+
style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" },
|
|
357
|
+
onSubmit: (event) => {
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
if (probe.trim()) void resolve(type, probe.trim());
|
|
360
|
+
},
|
|
361
|
+
children: [
|
|
362
|
+
/* @__PURE__ */ jsx(
|
|
363
|
+
"input",
|
|
364
|
+
{
|
|
365
|
+
"aria-label": t("templates.editor.whoReceives"),
|
|
366
|
+
style: { ...styles.input, width: 200 },
|
|
367
|
+
value: probe,
|
|
368
|
+
placeholder: t("templates.editor.whoPlaceholder"),
|
|
369
|
+
spellCheck: false,
|
|
370
|
+
onChange: (event) => setProbe(event.target.value)
|
|
371
|
+
}
|
|
372
|
+
),
|
|
373
|
+
/* @__PURE__ */ jsx("button", { type: "submit", style: { ...styles.rollbackButton, height: 36 }, children: t("templates.editor.check") }),
|
|
374
|
+
resolution && probe.trim() ? /* @__PURE__ */ jsxs("span", { style: { ...styles.meta, marginBottom: 0 }, role: "status", children: [
|
|
375
|
+
t("templates.editor.receives", {
|
|
376
|
+
requested: resolution.requested,
|
|
377
|
+
sent: resolution.sent
|
|
378
|
+
}),
|
|
379
|
+
" ",
|
|
380
|
+
resolution.source === "saved" ? t("templates.editor.sourceSaved") : t("templates.editor.sourceBuiltIn"),
|
|
381
|
+
" ",
|
|
382
|
+
resolution.chain.length > 0 ? `(${[...resolution.chain, resolution.defaultLocale].join(" \u2192 ")})` : ""
|
|
383
|
+
] }) : null
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
] }),
|
|
294
388
|
revisions.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles.revisions, children: [
|
|
295
389
|
/* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: t("templates.editor.history") }),
|
|
296
390
|
/* @__PURE__ */ jsx("ul", { style: styles.list, children: revisions.map((rev) => /* @__PURE__ */ jsxs("li", { style: styles.row, children: [
|
|
@@ -347,7 +441,9 @@ var styles = {
|
|
|
347
441
|
borderBottomColor: "var(--fonderie-text,#171717)",
|
|
348
442
|
cursor: "default"
|
|
349
443
|
},
|
|
350
|
-
tabInactive: {
|
|
444
|
+
tabInactive: { opacity: 0.55, textDecoration: "line-through" },
|
|
445
|
+
// Fonderie's built-in copy, not saved by the app — the list's dashed chip.
|
|
446
|
+
tabBuiltIn: { fontStyle: "italic" },
|
|
351
447
|
// Two columns, always: the editor on the left, the rendered result on the
|
|
352
448
|
// right. Stacking the preview under a long form hid it below the fold.
|
|
353
449
|
split: {
|
|
@@ -517,10 +613,10 @@ var styles = {
|
|
|
517
613
|
// src/screens/TemplateListScreen.tsx
|
|
518
614
|
import {
|
|
519
615
|
createAdminT as createAdminT2,
|
|
520
|
-
|
|
616
|
+
templateLanguages as templateLanguages2
|
|
521
617
|
} from "@fonderie/client";
|
|
522
|
-
import {
|
|
523
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
618
|
+
import { useTemplateCatalog as useTemplateCatalog2 } from "@fonderie/react-courier-admin";
|
|
619
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
524
620
|
function TemplateListScreen({
|
|
525
621
|
client,
|
|
526
622
|
onSelectTemplate,
|
|
@@ -528,8 +624,14 @@ function TemplateListScreen({
|
|
|
528
624
|
locale
|
|
529
625
|
}) {
|
|
530
626
|
const t = createAdminT2(locale);
|
|
531
|
-
const {
|
|
532
|
-
const
|
|
627
|
+
const { catalog, isLoading, error } = useTemplateCatalog2(client);
|
|
628
|
+
const emails = catalog?.emails ?? [];
|
|
629
|
+
const defaultLocale = catalog?.defaultLocale ?? "en-US";
|
|
630
|
+
const chipStyle = (l) => ({
|
|
631
|
+
...chip,
|
|
632
|
+
...l.saved ? {} : chipBuiltIn,
|
|
633
|
+
...l.active ? {} : chipOff
|
|
634
|
+
});
|
|
533
635
|
return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
|
|
534
636
|
/* @__PURE__ */ jsxs2(
|
|
535
637
|
"div",
|
|
@@ -543,8 +645,12 @@ function TemplateListScreen({
|
|
|
543
645
|
type: "button",
|
|
544
646
|
onClick: () => onCreateTemplate({
|
|
545
647
|
locales: [
|
|
546
|
-
...new Set(
|
|
547
|
-
|
|
648
|
+
...new Set(
|
|
649
|
+
emails.flatMap(
|
|
650
|
+
(e) => templateLanguages2(e, defaultLocale).filter((l) => l.locale !== null).map((l) => l.label)
|
|
651
|
+
)
|
|
652
|
+
)
|
|
653
|
+
].sort()
|
|
548
654
|
}),
|
|
549
655
|
style: styles2.newButton,
|
|
550
656
|
children: t("templates.list.newTemplate")
|
|
@@ -554,39 +660,47 @@ function TemplateListScreen({
|
|
|
554
660
|
}
|
|
555
661
|
),
|
|
556
662
|
/* @__PURE__ */ jsx2("p", { style: styles2.hint, children: t("templates.list.hint") }),
|
|
557
|
-
isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: t("templates.list.loading") }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */
|
|
558
|
-
/* @__PURE__ */
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
663
|
+
isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: t("templates.list.loading") }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
664
|
+
/* @__PURE__ */ jsx2("ul", { style: styles2.list, children: emails.map((email) => {
|
|
665
|
+
const languages = templateLanguages2(email, defaultLocale);
|
|
666
|
+
const primary = languages.find((l) => l.locale === null) ?? languages[0];
|
|
667
|
+
const open = (l) => onSelectTemplate?.({
|
|
668
|
+
type: email.type,
|
|
669
|
+
locale: l?.locale ?? null,
|
|
670
|
+
system: email.system
|
|
671
|
+
});
|
|
672
|
+
return (
|
|
673
|
+
// Columns, not a flex row: the language chips line up down the
|
|
674
|
+
// list whatever the email name or badge.
|
|
675
|
+
/* @__PURE__ */ jsxs2("li", { style: styles2.row, children: [
|
|
676
|
+
/* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => open(primary), style: styles2.rowButton, children: [
|
|
677
|
+
/* @__PURE__ */ jsx2("span", { style: styles2.type, children: email.type }),
|
|
678
|
+
email.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: t("templates.builtInBadge") }) : /* @__PURE__ */ jsx2("span", {})
|
|
679
|
+
] }),
|
|
680
|
+
/* @__PURE__ */ jsx2("span", { style: styles2.chips, children: languages.map((l) => /* @__PURE__ */ jsx2(
|
|
681
|
+
"button",
|
|
682
|
+
{
|
|
683
|
+
type: "button",
|
|
684
|
+
onClick: () => open(l),
|
|
685
|
+
style: chipStyle(l),
|
|
686
|
+
"aria-label": t("templates.list.openLocale", {
|
|
687
|
+
type: email.type,
|
|
688
|
+
locale: l.label
|
|
689
|
+
}),
|
|
690
|
+
title: l.active ? void 0 : t("common.status.inactive"),
|
|
691
|
+
children: l.label
|
|
692
|
+
},
|
|
693
|
+
l.label
|
|
694
|
+
)) }),
|
|
695
|
+
/* @__PURE__ */ jsxs2("span", { style: primary?.active === false ? styles2.inactive : styles2.active, children: [
|
|
568
696
|
/* @__PURE__ */ jsx2("span", { style: styles2.dot }),
|
|
569
|
-
|
|
697
|
+
primary?.active === false ? t("common.status.inactive") : t("common.status.active")
|
|
570
698
|
] })
|
|
571
|
-
]
|
|
572
|
-
}
|
|
573
|
-
),
|
|
574
|
-
/* @__PURE__ */ jsx2("span", { style: styles2.chips, children: group.entries.map((entry) => {
|
|
575
|
-
const name = entry.locale ?? t("templates.defaultChip");
|
|
576
|
-
return /* @__PURE__ */ jsx2(
|
|
577
|
-
"button",
|
|
578
|
-
{
|
|
579
|
-
type: "button",
|
|
580
|
-
onClick: () => onSelectTemplate?.(entry),
|
|
581
|
-
style: entry.active ? styles2.chip : styles2.chipInactive,
|
|
582
|
-
"aria-label": t("templates.list.openLocale", { type: group.type, locale: name }),
|
|
583
|
-
title: entry.active ? void 0 : t("common.status.inactive"),
|
|
584
|
-
children: name
|
|
585
|
-
},
|
|
586
|
-
entry.locale ?? ""
|
|
699
|
+
] }, email.type)
|
|
587
700
|
);
|
|
588
|
-
}) })
|
|
589
|
-
|
|
701
|
+
}) }),
|
|
702
|
+
/* @__PURE__ */ jsx2("p", { style: styles2.legend, children: t("templates.list.legend") })
|
|
703
|
+
] })
|
|
590
704
|
] });
|
|
591
705
|
}
|
|
592
706
|
var pill = {
|
|
@@ -600,6 +714,7 @@ var pill = {
|
|
|
600
714
|
lineHeight: 1.5,
|
|
601
715
|
minWidth: 64
|
|
602
716
|
};
|
|
717
|
+
var MONO = 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)';
|
|
603
718
|
var chip = {
|
|
604
719
|
height: 24,
|
|
605
720
|
boxSizing: "border-box",
|
|
@@ -609,9 +724,15 @@ var chip = {
|
|
|
609
724
|
background: "var(--fonderie-surface,#fff)",
|
|
610
725
|
color: "var(--fonderie-text,#171717)",
|
|
611
726
|
fontSize: 12,
|
|
612
|
-
fontFamily:
|
|
727
|
+
fontFamily: MONO,
|
|
613
728
|
cursor: "pointer"
|
|
614
729
|
};
|
|
730
|
+
var chipBuiltIn = {
|
|
731
|
+
borderStyle: "dashed",
|
|
732
|
+
background: "transparent",
|
|
733
|
+
color: "var(--fonderie-text-muted,#5c5c5c)"
|
|
734
|
+
};
|
|
735
|
+
var chipOff = { opacity: 0.5, textDecoration: "line-through" };
|
|
615
736
|
var styles2 = {
|
|
616
737
|
container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
|
|
617
738
|
title: {
|
|
@@ -622,6 +743,7 @@ var styles2 = {
|
|
|
622
743
|
lineHeight: 1.25
|
|
623
744
|
},
|
|
624
745
|
hint: { fontSize: 13.5, color: "var(--fonderie-text-muted,#5c5c5c)", margin: "4px 0 24px" },
|
|
746
|
+
legend: { fontSize: 12.5, color: "var(--fonderie-text-muted,#5c5c5c)", margin: "10px 2px 0" },
|
|
625
747
|
status: { padding: "24px 0", color: "var(--fonderie-text-muted,#5c5c5c)", fontSize: 13.5 },
|
|
626
748
|
error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
|
|
627
749
|
list: {
|
|
@@ -634,20 +756,20 @@ var styles2 = {
|
|
|
634
756
|
overflow: "hidden",
|
|
635
757
|
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
|
|
636
758
|
},
|
|
759
|
+
// name + badge | languages | status — fixed tracks so every column aligns.
|
|
637
760
|
row: {
|
|
638
|
-
display: "
|
|
761
|
+
display: "grid",
|
|
762
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(180px, 280px) 88px",
|
|
639
763
|
alignItems: "center",
|
|
640
|
-
gap:
|
|
764
|
+
gap: 16,
|
|
641
765
|
paddingRight: 16,
|
|
642
766
|
borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)"
|
|
643
767
|
},
|
|
644
768
|
rowButton: {
|
|
645
|
-
flex: 1,
|
|
646
769
|
minWidth: 0,
|
|
647
770
|
display: "flex",
|
|
648
|
-
justifyContent: "space-between",
|
|
649
771
|
alignItems: "center",
|
|
650
|
-
gap:
|
|
772
|
+
gap: 12,
|
|
651
773
|
padding: "13px 16px",
|
|
652
774
|
background: "none",
|
|
653
775
|
border: "none",
|
|
@@ -659,12 +781,12 @@ var styles2 = {
|
|
|
659
781
|
type: {
|
|
660
782
|
fontSize: 13,
|
|
661
783
|
fontWeight: 600,
|
|
662
|
-
|
|
663
|
-
|
|
784
|
+
overflow: "hidden",
|
|
785
|
+
textOverflow: "ellipsis",
|
|
786
|
+
whiteSpace: "nowrap",
|
|
787
|
+
fontFamily: MONO
|
|
664
788
|
},
|
|
665
|
-
chips: { display: "flex", flexWrap: "wrap",
|
|
666
|
-
chip,
|
|
667
|
-
chipInactive: { ...chip, opacity: 0.5, textDecoration: "line-through" },
|
|
789
|
+
chips: { display: "flex", flexWrap: "wrap", gap: 6 },
|
|
668
790
|
dot: { width: 6, height: 6, borderRadius: 999, background: "currentColor" },
|
|
669
791
|
builtIn: {
|
|
670
792
|
fontSize: 11,
|
|
@@ -672,7 +794,8 @@ var styles2 = {
|
|
|
672
794
|
padding: "1px 7px",
|
|
673
795
|
borderRadius: 999,
|
|
674
796
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
675
|
-
color: "var(--fonderie-text-muted,#5c5c5c)"
|
|
797
|
+
color: "var(--fonderie-text-muted,#5c5c5c)",
|
|
798
|
+
flexShrink: 0
|
|
676
799
|
},
|
|
677
800
|
newButton: {
|
|
678
801
|
display: "inline-flex",
|
|
@@ -706,14 +829,18 @@ import {
|
|
|
706
829
|
FonderieApiError,
|
|
707
830
|
createAdminT as createAdminT3
|
|
708
831
|
} from "@fonderie/client";
|
|
709
|
-
import {
|
|
710
|
-
import {
|
|
832
|
+
import { useTemplatePreview as useTemplatePreview2 } from "@fonderie/react-courier-admin";
|
|
833
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
834
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
711
835
|
var TYPE_RE = /^[a-z0-9][a-z0-9_-]{0,80}$/;
|
|
712
836
|
var LOCALE_RE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
|
|
837
|
+
var IMPLICIT2 = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
|
|
838
|
+
var PREVIEW_DEBOUNCE_MS2 = 500;
|
|
713
839
|
function TemplateCreateScreen({
|
|
714
840
|
client,
|
|
715
841
|
type,
|
|
716
842
|
locales = [],
|
|
843
|
+
defaultLocale,
|
|
717
844
|
onCreated,
|
|
718
845
|
locale
|
|
719
846
|
}) {
|
|
@@ -726,14 +853,48 @@ function TemplateCreateScreen({
|
|
|
726
853
|
const [text, setText] = useState2("");
|
|
727
854
|
const [error, setError] = useState2(null);
|
|
728
855
|
const [busy, setBusy] = useState2(false);
|
|
856
|
+
const [source, setSource] = useState2(
|
|
857
|
+
null
|
|
858
|
+
);
|
|
859
|
+
const { preview, isPreviewing, error: previewError, renderPreview } = useTemplatePreview2(client);
|
|
860
|
+
const variables = useRef2([]);
|
|
729
861
|
useEffect2(() => {
|
|
730
862
|
if (!type) return;
|
|
731
|
-
client.getTemplate(type, null).then(({ result }) => {
|
|
863
|
+
client.getTemplate(type, null).catch(() => client.getBuiltInTemplate(type, null)).then(({ result }) => {
|
|
732
864
|
setSubject(result.subject ?? "");
|
|
733
865
|
setHtml(result.html ?? "");
|
|
734
866
|
setText(result.text);
|
|
867
|
+
setSource({ subject: result.subject ?? "", html: result.html ?? "", text: result.text });
|
|
735
868
|
}).catch(() => void 0);
|
|
736
869
|
}, [client, type]);
|
|
870
|
+
const previewType = addingLocale ? type : newType.trim();
|
|
871
|
+
const previewLocale = LOCALE_RE.test(targetLocale.trim()) ? targetLocale.trim() : null;
|
|
872
|
+
useEffect2(() => {
|
|
873
|
+
if (!TYPE_RE.test(previewType) || !text.trim()) return;
|
|
874
|
+
const timer = setTimeout(async () => {
|
|
875
|
+
const input = { text, ...subject ? { subject } : {}, ...html ? { html } : {} };
|
|
876
|
+
const render = () => renderPreview(
|
|
877
|
+
previewType,
|
|
878
|
+
{ ...input, data: Object.fromEntries(variables.current.map((v) => [v, v])) },
|
|
879
|
+
previewLocale
|
|
880
|
+
);
|
|
881
|
+
try {
|
|
882
|
+
const result = await render();
|
|
883
|
+
const used = result.variables.filter((v) => !IMPLICIT2.has(v));
|
|
884
|
+
if (used.some((v) => !variables.current.includes(v))) {
|
|
885
|
+
variables.current = used;
|
|
886
|
+
await render();
|
|
887
|
+
}
|
|
888
|
+
} catch {
|
|
889
|
+
}
|
|
890
|
+
}, PREVIEW_DEBOUNCE_MS2);
|
|
891
|
+
return () => clearTimeout(timer);
|
|
892
|
+
}, [renderPreview, previewType, previewLocale, subject, html, text]);
|
|
893
|
+
const untranslated = source ? [
|
|
894
|
+
subject && subject === source.subject ? tr("templates.subject") : null,
|
|
895
|
+
html && html === source.html ? tr("templates.htmlBody") : null,
|
|
896
|
+
text === source.text ? tr("templates.textBody") : null
|
|
897
|
+
].filter((f) => f !== null) : [];
|
|
737
898
|
const submit = async (e) => {
|
|
738
899
|
e.preventDefault();
|
|
739
900
|
setError(null);
|
|
@@ -742,6 +903,9 @@ function TemplateCreateScreen({
|
|
|
742
903
|
if (!TYPE_RE.test(t)) return setError(tr("templates.create.errorType"));
|
|
743
904
|
if (addingLocale && !l) return setError(tr("templates.create.errorLocaleRequired"));
|
|
744
905
|
if (l && !LOCALE_RE.test(l)) return setError(tr("templates.create.errorLocale"));
|
|
906
|
+
if (l && defaultLocale && l.toLowerCase() === defaultLocale.toLowerCase()) {
|
|
907
|
+
return setError(tr("templates.create.errorDefaultLocale", { locale: defaultLocale }));
|
|
908
|
+
}
|
|
745
909
|
if (!text.trim()) return setError(tr("templates.create.errorText"));
|
|
746
910
|
setBusy(true);
|
|
747
911
|
try {
|
|
@@ -769,75 +933,140 @@ function TemplateCreateScreen({
|
|
|
769
933
|
return /* @__PURE__ */ jsxs3("div", { style: styles3.container, children: [
|
|
770
934
|
/* @__PURE__ */ jsx3("h1", { style: styles3.title, children: addingLocale ? tr("templates.create.titleLocale", { type }) : tr("templates.create.titleNew") }),
|
|
771
935
|
/* @__PURE__ */ jsx3("p", { style: styles3.meta, children: addingLocale ? tr("templates.create.hintLocale") : tr("templates.create.hintNew") }),
|
|
772
|
-
/* @__PURE__ */ jsxs3("
|
|
773
|
-
|
|
774
|
-
/* @__PURE__ */
|
|
936
|
+
/* @__PURE__ */ jsxs3("div", { style: styles3.split, children: [
|
|
937
|
+
/* @__PURE__ */ jsxs3("form", { style: styles3.form, onSubmit: (e) => void submit(e), children: [
|
|
938
|
+
!addingLocale ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
939
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: tr("templates.create.type") }),
|
|
940
|
+
/* @__PURE__ */ jsx3(
|
|
941
|
+
"input",
|
|
942
|
+
{
|
|
943
|
+
id: "new-template-type",
|
|
944
|
+
style: styles3.input,
|
|
945
|
+
value: newType,
|
|
946
|
+
placeholder: "weekly-digest",
|
|
947
|
+
autoComplete: "off",
|
|
948
|
+
spellCheck: false,
|
|
949
|
+
onChange: (e) => setNewType(e.target.value)
|
|
950
|
+
}
|
|
951
|
+
)
|
|
952
|
+
] }) : null,
|
|
953
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-locale", children: addingLocale ? tr("templates.create.locale") : tr("templates.create.localeOptional") }),
|
|
775
954
|
/* @__PURE__ */ jsx3(
|
|
776
955
|
"input",
|
|
777
956
|
{
|
|
778
|
-
id: "new-template-
|
|
957
|
+
id: "new-template-locale",
|
|
779
958
|
style: styles3.input,
|
|
780
|
-
value:
|
|
781
|
-
placeholder: "
|
|
959
|
+
value: targetLocale,
|
|
960
|
+
placeholder: "fr",
|
|
961
|
+
list: "new-template-locales",
|
|
782
962
|
autoComplete: "off",
|
|
783
963
|
spellCheck: false,
|
|
784
|
-
onChange: (e) =>
|
|
964
|
+
onChange: (e) => setTargetLocale(e.target.value)
|
|
785
965
|
}
|
|
786
|
-
)
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
966
|
+
),
|
|
967
|
+
/* @__PURE__ */ jsx3("datalist", { id: "new-template-locales", children: [...new Set(locales)].map((l) => /* @__PURE__ */ jsx3("option", { value: l }, l)) }),
|
|
968
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-subject", children: tr("templates.subject") }),
|
|
969
|
+
/* @__PURE__ */ jsx3(
|
|
970
|
+
"input",
|
|
971
|
+
{
|
|
972
|
+
id: "new-template-subject",
|
|
973
|
+
style: styles3.input,
|
|
974
|
+
value: subject,
|
|
975
|
+
onChange: (e) => setSubject(e.target.value)
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-html", children: tr("templates.htmlBody") }),
|
|
979
|
+
/* @__PURE__ */ jsx3(
|
|
980
|
+
"textarea",
|
|
981
|
+
{
|
|
982
|
+
id: "new-template-html",
|
|
983
|
+
style: { ...styles3.textarea, minHeight: 160 },
|
|
984
|
+
value: html,
|
|
985
|
+
onChange: (e) => setHtml(e.target.value)
|
|
986
|
+
}
|
|
987
|
+
),
|
|
988
|
+
/* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-text", children: tr("templates.textBody") }),
|
|
989
|
+
/* @__PURE__ */ jsx3(
|
|
990
|
+
"textarea",
|
|
991
|
+
{
|
|
992
|
+
id: "new-template-text",
|
|
993
|
+
style: { ...styles3.textarea, minHeight: 100 },
|
|
994
|
+
value: text,
|
|
995
|
+
onChange: (e) => setText(e.target.value)
|
|
996
|
+
}
|
|
997
|
+
),
|
|
998
|
+
untranslated.length > 0 ? /* @__PURE__ */ jsx3("p", { style: styles3.notice, children: tr("templates.create.untranslated", { fields: untranslated.join(", ") }) }) : null,
|
|
999
|
+
error ? /* @__PURE__ */ jsx3("p", { style: styles3.error, role: "alert", children: error }) : null,
|
|
1000
|
+
/* @__PURE__ */ jsx3("button", { type: "submit", disabled: busy, style: styles3.button, children: busy ? tr("templates.create.creating") : addingLocale ? tr("templates.create.submitLocale") : tr("templates.create.submitNew") })
|
|
1001
|
+
] }),
|
|
1002
|
+
/* @__PURE__ */ jsxs3("div", { style: styles3.previewColumn, children: [
|
|
1003
|
+
/* @__PURE__ */ jsxs3("div", { style: styles3.previewHeader, children: [
|
|
1004
|
+
/* @__PURE__ */ jsx3("span", { style: styles3.label, children: tr("templates.editor.preview") }),
|
|
1005
|
+
/* @__PURE__ */ jsx3("span", { style: styles3.meta, children: isPreviewing ? tr("templates.editor.rendering") : tr("templates.editor.live") })
|
|
1006
|
+
] }),
|
|
1007
|
+
previewError ? /* @__PURE__ */ jsx3("p", { style: styles3.error, role: "alert", children: previewError.explanation }) : null,
|
|
1008
|
+
preview?.subject ? /* @__PURE__ */ jsx3("p", { style: styles3.previewSubject, children: preview.subject }) : null,
|
|
1009
|
+
preview?.html ? (
|
|
1010
|
+
// sandbox="" — no scripts, no same-origin: operator-authored HTML must
|
|
1011
|
+
// never execute in the console's origin.
|
|
1012
|
+
/* @__PURE__ */ jsx3(
|
|
1013
|
+
"iframe",
|
|
1014
|
+
{
|
|
1015
|
+
title: tr("templates.editor.previewTitle"),
|
|
1016
|
+
style: styles3.previewFrame,
|
|
1017
|
+
sandbox: "",
|
|
1018
|
+
srcDoc: preview.html
|
|
1019
|
+
}
|
|
1020
|
+
)
|
|
1021
|
+
) : preview ? /* @__PURE__ */ jsx3("pre", { style: styles3.previewText, children: preview.text }) : /* @__PURE__ */ jsx3("p", { style: styles3.meta, children: tr("templates.editor.nothingRendered") })
|
|
1022
|
+
] })
|
|
835
1023
|
] })
|
|
836
1024
|
] });
|
|
837
1025
|
}
|
|
838
|
-
var
|
|
1026
|
+
var MONO2 = 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)';
|
|
839
1027
|
var styles3 = {
|
|
840
|
-
container: { padding: "8px 40px 64px", maxWidth:
|
|
1028
|
+
container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
|
|
1029
|
+
// The editor's layout: the form on the left, the render pinned on the right.
|
|
1030
|
+
split: {
|
|
1031
|
+
display: "grid",
|
|
1032
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
|
|
1033
|
+
gap: 24,
|
|
1034
|
+
alignItems: "start"
|
|
1035
|
+
},
|
|
1036
|
+
previewColumn: {
|
|
1037
|
+
minWidth: 0,
|
|
1038
|
+
display: "flex",
|
|
1039
|
+
flexDirection: "column",
|
|
1040
|
+
position: "sticky",
|
|
1041
|
+
top: 16
|
|
1042
|
+
},
|
|
1043
|
+
previewHeader: { display: "flex", justifyContent: "space-between", alignItems: "center" },
|
|
1044
|
+
previewSubject: { fontSize: 14, fontWeight: 600, margin: "8px 0" },
|
|
1045
|
+
previewFrame: {
|
|
1046
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))",
|
|
1047
|
+
width: "100%",
|
|
1048
|
+
height: 520,
|
|
1049
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
1050
|
+
borderRadius: 8,
|
|
1051
|
+
background: "var(--fonderie-surface,#fff)"
|
|
1052
|
+
},
|
|
1053
|
+
previewText: {
|
|
1054
|
+
whiteSpace: "pre-wrap",
|
|
1055
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
1056
|
+
borderRadius: 8,
|
|
1057
|
+
padding: 12,
|
|
1058
|
+
fontSize: 13,
|
|
1059
|
+
fontFamily: MONO2
|
|
1060
|
+
},
|
|
1061
|
+
// The console's warning token (the shell's amber), readable in both themes.
|
|
1062
|
+
notice: {
|
|
1063
|
+
fontSize: 13,
|
|
1064
|
+
color: "var(--fonderie-text,#171717)",
|
|
1065
|
+
background: "color-mix(in srgb, var(--fonderie-warning,#f5a623) 16%, transparent)",
|
|
1066
|
+
borderRadius: 6,
|
|
1067
|
+
padding: "8px 12px",
|
|
1068
|
+
margin: "12px 0 0"
|
|
1069
|
+
},
|
|
841
1070
|
title: {
|
|
842
1071
|
fontSize: 22,
|
|
843
1072
|
fontWeight: 600,
|
|
@@ -880,7 +1109,7 @@ var styles3 = {
|
|
|
880
1109
|
lineHeight: 1.55,
|
|
881
1110
|
background: "var(--fonderie-surface,#fff)",
|
|
882
1111
|
color: "var(--fonderie-text,#171717)",
|
|
883
|
-
fontFamily:
|
|
1112
|
+
fontFamily: MONO2
|
|
884
1113
|
},
|
|
885
1114
|
error: {
|
|
886
1115
|
color: "var(--fonderie-danger,#e00)",
|