@fonderie/react-courier-admin-screens 0.4.1 → 0.6.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 +200 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +201 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var import_react_courier_admin = require("@fonderie/react-courier-admin");
|
|
|
30
30
|
var import_react = require("react");
|
|
31
31
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
32
32
|
var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
|
|
33
|
+
var PREVIEW_DEBOUNCE_MS = 500;
|
|
33
34
|
function TemplateEditorScreen({
|
|
34
35
|
client,
|
|
35
36
|
type,
|
|
@@ -80,10 +81,15 @@ function TemplateEditorScreen({
|
|
|
80
81
|
setSampleJson(JSON.stringify(seeded, null, 2));
|
|
81
82
|
}
|
|
82
83
|
}, [renderPreview, type, text, subject, html, locale, sampleJson]);
|
|
84
|
+
const runRef = (0, import_react.useRef)(run);
|
|
85
|
+
runRef.current = run;
|
|
83
86
|
const loadedType = template ? `${type}:${locale ?? ""}` : null;
|
|
84
87
|
(0, import_react.useEffect)(() => {
|
|
85
|
-
if (loadedType)
|
|
86
|
-
|
|
88
|
+
if (!loadedType) return;
|
|
89
|
+
const timer = setTimeout(() => void runRef.current(), PREVIEW_DEBOUNCE_MS);
|
|
90
|
+
return () => clearTimeout(timer);
|
|
91
|
+
}, [loadedType, subject, html, text, sampleJson]);
|
|
92
|
+
const dirty = !!template && (subject !== (template.subject ?? "") || html !== (template.html ?? "") || text !== template.text || active !== template.active);
|
|
87
93
|
const handleSubmit = async (event) => {
|
|
88
94
|
event.preventDefault();
|
|
89
95
|
setIsSaving(true);
|
|
@@ -160,7 +166,18 @@ function TemplateEditorScreen({
|
|
|
160
166
|
"Active"
|
|
161
167
|
] }),
|
|
162
168
|
saveError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.error, role: "alert", children: saveError.explanation }),
|
|
163
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.saveRow, children: [
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
171
|
+
"button",
|
|
172
|
+
{
|
|
173
|
+
type: "submit",
|
|
174
|
+
disabled: isSaving || !dirty,
|
|
175
|
+
style: dirty ? styles.button : styles.buttonDisabled,
|
|
176
|
+
children: isSaving ? "Saving\u2026" : "Save"
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
!dirty && !isSaving && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.meta, children: "No changes to save" })
|
|
180
|
+
] })
|
|
164
181
|
] }),
|
|
165
182
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { style: styles.label, htmlFor: "template-sample", children: "Sample data" }),
|
|
166
183
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -176,10 +193,10 @@ function TemplateEditorScreen({
|
|
|
176
193
|
),
|
|
177
194
|
sampleError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.error, role: "alert", children: sampleError })
|
|
178
195
|
] }),
|
|
179
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.
|
|
196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.previewColumn, children: [
|
|
180
197
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.previewHeader, children: [
|
|
181
198
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.label, children: "Preview" }),
|
|
182
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.meta, children: isPreviewing ? "Rendering\u2026" : "Live" })
|
|
183
200
|
] }),
|
|
184
201
|
previewError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.error, role: "alert", children: previewError.explanation }),
|
|
185
202
|
preview?.subject && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.previewSubject, children: preview.subject }),
|
|
@@ -225,9 +242,25 @@ function TemplateEditorScreen({
|
|
|
225
242
|
] });
|
|
226
243
|
}
|
|
227
244
|
var styles = {
|
|
228
|
-
container: { padding:
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
|
|
246
|
+
// Two columns, always: the editor on the left, the rendered result on the
|
|
247
|
+
// right. Stacking the preview under a long form hid it below the fold.
|
|
248
|
+
split: {
|
|
249
|
+
display: "grid",
|
|
250
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
|
|
251
|
+
gap: 24,
|
|
252
|
+
alignItems: "start"
|
|
253
|
+
},
|
|
254
|
+
column: { minWidth: 0, display: "flex", flexDirection: "column" },
|
|
255
|
+
// Pinned while the form scrolls, so the preview stays in view as you edit.
|
|
256
|
+
previewColumn: {
|
|
257
|
+
minWidth: 0,
|
|
258
|
+
display: "flex",
|
|
259
|
+
flexDirection: "column",
|
|
260
|
+
position: "sticky",
|
|
261
|
+
top: 16
|
|
262
|
+
},
|
|
263
|
+
saveRow: { display: "flex", alignItems: "center", gap: 12, marginTop: 16 },
|
|
231
264
|
previewHeader: {
|
|
232
265
|
display: "flex",
|
|
233
266
|
justifyContent: "space-between",
|
|
@@ -236,6 +269,7 @@ var styles = {
|
|
|
236
269
|
},
|
|
237
270
|
previewSubject: { fontSize: 14, fontWeight: 600, margin: "8px 0" },
|
|
238
271
|
previewFrame: {
|
|
272
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))",
|
|
239
273
|
width: "100%",
|
|
240
274
|
height: 520,
|
|
241
275
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
@@ -250,50 +284,127 @@ var styles = {
|
|
|
250
284
|
fontSize: 13,
|
|
251
285
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
252
286
|
},
|
|
253
|
-
title: {
|
|
254
|
-
|
|
287
|
+
title: {
|
|
288
|
+
fontSize: 22,
|
|
289
|
+
fontWeight: 600,
|
|
290
|
+
margin: "8px 0 4px",
|
|
291
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)",
|
|
292
|
+
lineHeight: 1.25
|
|
293
|
+
},
|
|
294
|
+
meta: { fontSize: 13, color: "var(--fonderie-text-muted,#5c5c5c)", marginBottom: 20 },
|
|
255
295
|
status: { padding: 24, textAlign: "center", color: "var(--fonderie-text-muted,#5c5c5c)" },
|
|
256
|
-
error: {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
296
|
+
error: {
|
|
297
|
+
color: "var(--fonderie-danger,#e00)",
|
|
298
|
+
background: "color-mix(in srgb, var(--fonderie-danger,#e00) 8%, transparent)",
|
|
299
|
+
borderRadius: 6,
|
|
300
|
+
padding: "8px 12px",
|
|
301
|
+
margin: "8px 0",
|
|
302
|
+
fontSize: 13.5
|
|
303
|
+
},
|
|
304
|
+
form: {
|
|
305
|
+
display: "flex",
|
|
306
|
+
flexDirection: "column",
|
|
307
|
+
gap: 4,
|
|
308
|
+
background: "var(--fonderie-surface,#fff)",
|
|
309
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
310
|
+
borderRadius: "var(--fonderie-radius-lg,8px)",
|
|
311
|
+
padding: "6px 20px 20px",
|
|
312
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
|
|
313
|
+
},
|
|
314
|
+
label: { fontSize: 13, fontWeight: 500, marginTop: 14, marginBottom: 2 },
|
|
315
|
+
input: {
|
|
316
|
+
height: 36,
|
|
317
|
+
boxSizing: "border-box",
|
|
318
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
319
|
+
borderRadius: 6,
|
|
320
|
+
padding: "0 10px",
|
|
321
|
+
fontSize: 13.5,
|
|
322
|
+
fontFamily: "inherit",
|
|
323
|
+
background: "var(--fonderie-surface,#fff)",
|
|
324
|
+
color: "var(--fonderie-text,#171717)"
|
|
325
|
+
},
|
|
260
326
|
textarea: {
|
|
261
327
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
262
|
-
borderRadius:
|
|
328
|
+
borderRadius: 6,
|
|
263
329
|
padding: 10,
|
|
264
|
-
fontSize:
|
|
330
|
+
fontSize: 13,
|
|
331
|
+
lineHeight: 1.55,
|
|
332
|
+
background: "var(--fonderie-surface,#fff)",
|
|
333
|
+
color: "var(--fonderie-text,#171717)",
|
|
265
334
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
266
335
|
},
|
|
267
336
|
checkboxLabel: { display: "flex", alignItems: "center", gap: 8, marginTop: 12, fontSize: 14 },
|
|
268
337
|
button: {
|
|
269
|
-
|
|
338
|
+
display: "inline-flex",
|
|
339
|
+
alignItems: "center",
|
|
340
|
+
height: 36,
|
|
341
|
+
boxSizing: "border-box",
|
|
270
342
|
backgroundColor: "var(--fonderie-text,#171717)",
|
|
271
343
|
color: "var(--fonderie-surface,#fff)",
|
|
272
|
-
padding: "
|
|
273
|
-
borderRadius:
|
|
274
|
-
border: "
|
|
275
|
-
fontSize:
|
|
344
|
+
padding: "0 16px",
|
|
345
|
+
borderRadius: 6,
|
|
346
|
+
border: "1px solid var(--fonderie-text,#171717)",
|
|
347
|
+
fontSize: 13.5,
|
|
276
348
|
fontWeight: 600,
|
|
349
|
+
fontFamily: "inherit",
|
|
277
350
|
cursor: "pointer",
|
|
278
351
|
alignSelf: "flex-start"
|
|
279
352
|
},
|
|
353
|
+
buttonDisabled: {
|
|
354
|
+
display: "inline-flex",
|
|
355
|
+
alignItems: "center",
|
|
356
|
+
height: 36,
|
|
357
|
+
boxSizing: "border-box",
|
|
358
|
+
backgroundColor: "var(--fonderie-text,#171717)",
|
|
359
|
+
color: "var(--fonderie-surface,#fff)",
|
|
360
|
+
padding: "0 16px",
|
|
361
|
+
borderRadius: 6,
|
|
362
|
+
border: "1px solid var(--fonderie-text,#171717)",
|
|
363
|
+
fontSize: 13.5,
|
|
364
|
+
fontWeight: 600,
|
|
365
|
+
fontFamily: "inherit",
|
|
366
|
+
alignSelf: "flex-start",
|
|
367
|
+
opacity: 0.45,
|
|
368
|
+
cursor: "not-allowed"
|
|
369
|
+
},
|
|
280
370
|
revisions: { marginTop: 32 },
|
|
281
|
-
subtitle: {
|
|
282
|
-
|
|
371
|
+
subtitle: {
|
|
372
|
+
fontSize: 15,
|
|
373
|
+
fontWeight: 600,
|
|
374
|
+
margin: "0 0 10px",
|
|
375
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
|
|
376
|
+
},
|
|
377
|
+
list: {
|
|
378
|
+
listStyle: "none",
|
|
379
|
+
padding: 0,
|
|
380
|
+
margin: 0,
|
|
381
|
+
background: "var(--fonderie-surface,#fff)",
|
|
382
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
383
|
+
borderRadius: "var(--fonderie-radius-lg,8px)",
|
|
384
|
+
overflow: "hidden"
|
|
385
|
+
},
|
|
283
386
|
row: {
|
|
284
387
|
display: "flex",
|
|
285
388
|
justifyContent: "space-between",
|
|
286
389
|
alignItems: "center",
|
|
287
|
-
|
|
390
|
+
gap: 12,
|
|
391
|
+
padding: "9px 16px",
|
|
288
392
|
borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)",
|
|
289
393
|
fontSize: 13
|
|
290
394
|
},
|
|
291
395
|
rollbackButton: {
|
|
292
|
-
|
|
396
|
+
display: "inline-flex",
|
|
397
|
+
alignItems: "center",
|
|
398
|
+
height: 28,
|
|
399
|
+
boxSizing: "border-box",
|
|
400
|
+
background: "var(--fonderie-surface,#fff)",
|
|
401
|
+
color: "var(--fonderie-text,#171717)",
|
|
293
402
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
294
|
-
borderRadius:
|
|
295
|
-
padding: "
|
|
296
|
-
fontSize: 12,
|
|
403
|
+
borderRadius: 6,
|
|
404
|
+
padding: "0 10px",
|
|
405
|
+
fontSize: 12.5,
|
|
406
|
+
fontWeight: 500,
|
|
407
|
+
fontFamily: "inherit",
|
|
297
408
|
cursor: "pointer"
|
|
298
409
|
}
|
|
299
410
|
};
|
|
@@ -303,12 +414,10 @@ var import_react_courier_admin2 = require("@fonderie/react-courier-admin");
|
|
|
303
414
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
304
415
|
function TemplateListScreen({ client, onSelectTemplate }) {
|
|
305
416
|
const { templates, isLoading, error } = (0, import_react_courier_admin2.useTemplates)(client);
|
|
306
|
-
if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles2.status, children: "Loading templates\u2026" });
|
|
307
|
-
if (error)
|
|
308
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles2.error, role: "alert", children: error.explanation });
|
|
309
417
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles2.container, children: [
|
|
310
418
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { style: styles2.title, children: "Templates" }),
|
|
311
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
419
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles2.hint, children: "Every email the app sends. Open one to edit its copy and preview it live." }),
|
|
420
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles2.status, children: "Loading templates\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: styles2.list, children: templates.map((template) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { style: styles2.row, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
312
421
|
"button",
|
|
313
422
|
{
|
|
314
423
|
type: "button",
|
|
@@ -316,35 +425,82 @@ function TemplateListScreen({ client, onSelectTemplate }) {
|
|
|
316
425
|
style: styles2.rowButton,
|
|
317
426
|
children: [
|
|
318
427
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles2.type, children: template.type }),
|
|
319
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles2.locale, children: template.locale ?? "
|
|
320
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
428
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles2.locale, children: template.locale ?? "default locale" }),
|
|
429
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: template.active ? styles2.active : styles2.inactive, children: [
|
|
430
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles2.dot }),
|
|
431
|
+
template.active ? "active" : "inactive"
|
|
432
|
+
] })
|
|
321
433
|
]
|
|
322
434
|
}
|
|
323
435
|
) }, `${template.type}:${template.locale ?? "base"}`)) })
|
|
324
436
|
] });
|
|
325
437
|
}
|
|
438
|
+
var pill = {
|
|
439
|
+
display: "inline-flex",
|
|
440
|
+
alignItems: "center",
|
|
441
|
+
gap: 6,
|
|
442
|
+
borderRadius: 999,
|
|
443
|
+
padding: "2px 9px 2px 8px",
|
|
444
|
+
fontSize: 12,
|
|
445
|
+
fontWeight: 600,
|
|
446
|
+
lineHeight: 1.5,
|
|
447
|
+
minWidth: 64
|
|
448
|
+
};
|
|
326
449
|
var styles2 = {
|
|
327
|
-
container: { padding:
|
|
328
|
-
title: {
|
|
329
|
-
|
|
450
|
+
container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
|
|
451
|
+
title: {
|
|
452
|
+
fontSize: 22,
|
|
453
|
+
fontWeight: 600,
|
|
454
|
+
margin: 0,
|
|
455
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)",
|
|
456
|
+
lineHeight: 1.25
|
|
457
|
+
},
|
|
458
|
+
hint: { fontSize: 13.5, color: "var(--fonderie-text-muted,#5c5c5c)", margin: "4px 0 24px" },
|
|
459
|
+
status: { padding: "24px 0", color: "var(--fonderie-text-muted,#5c5c5c)", fontSize: 13.5 },
|
|
330
460
|
error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
|
|
331
|
-
list: {
|
|
461
|
+
list: {
|
|
462
|
+
listStyle: "none",
|
|
463
|
+
padding: 0,
|
|
464
|
+
margin: 0,
|
|
465
|
+
background: "var(--fonderie-surface,#fff)",
|
|
466
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
467
|
+
borderRadius: "var(--fonderie-radius-lg,8px)",
|
|
468
|
+
overflow: "hidden",
|
|
469
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
|
|
470
|
+
},
|
|
332
471
|
row: { borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)" },
|
|
333
472
|
rowButton: {
|
|
334
473
|
width: "100%",
|
|
335
474
|
display: "flex",
|
|
336
475
|
justifyContent: "space-between",
|
|
337
476
|
alignItems: "center",
|
|
338
|
-
|
|
477
|
+
gap: 16,
|
|
478
|
+
padding: "13px 16px",
|
|
339
479
|
background: "none",
|
|
340
480
|
border: "none",
|
|
341
481
|
cursor: "pointer",
|
|
342
|
-
textAlign: "left"
|
|
482
|
+
textAlign: "left",
|
|
483
|
+
color: "var(--fonderie-text,#171717)",
|
|
484
|
+
fontFamily: "inherit"
|
|
343
485
|
},
|
|
344
|
-
type: {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
486
|
+
type: {
|
|
487
|
+
fontSize: 13,
|
|
488
|
+
fontWeight: 600,
|
|
489
|
+
flex: 1,
|
|
490
|
+
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
491
|
+
},
|
|
492
|
+
locale: { fontSize: 12.5, color: "var(--fonderie-text-muted,#5c5c5c)" },
|
|
493
|
+
dot: { width: 6, height: 6, borderRadius: 999, background: "currentColor" },
|
|
494
|
+
active: {
|
|
495
|
+
...pill,
|
|
496
|
+
color: "var(--fonderie-accent-strong,#009767)",
|
|
497
|
+
background: "color-mix(in srgb, var(--fonderie-accent-strong,#009767) 13%, transparent)"
|
|
498
|
+
},
|
|
499
|
+
inactive: {
|
|
500
|
+
...pill,
|
|
501
|
+
color: "var(--fonderie-text-muted,#5c5c5c)",
|
|
502
|
+
background: "color-mix(in srgb, var(--fonderie-text-muted,#5c5c5c) 10%, transparent)"
|
|
503
|
+
}
|
|
348
504
|
};
|
|
349
505
|
// Annotate the CommonJS export names for ESM import in node:
|
|
350
506
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/screens/TemplateEditorScreen.tsx","../src/screens/TemplateListScreen.tsx"],"sourcesContent":["export type { ITemplateEditorScreenProps, ITemplateListScreenProps } from './screens';\nexport { TemplateEditorScreen, TemplateListScreen } from './screens';\n","import type { CourierAdminClient, FonderieApiError, ISetTemplateInput } from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/react-courier-admin';\nimport type { CSSProperties, FormEvent } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nexport interface ITemplateEditorScreenProps {\n\tclient: CourierAdminClient;\n\ttype: string;\n\tlocale?: string | null;\n\tonSaved?: () => void;\n}\n\nexport function TemplateEditorScreen({\n\tclient,\n\ttype,\n\tlocale,\n\tonSaved,\n}: ITemplateEditorScreenProps) {\n\tconst { template, isLoading, error, refresh } = useTemplate(client, type, locale);\n\t// Saves go through the list hook (which re-fetches the template list after\n\t// each write); mounting it adds a template-list fetch to this single-template\n\t// editor — acceptable for an admin dashboard. Its isLoading/error track that\n\t// list fetch, so the save action keeps local state.\n\tconst { saveTemplate } = useTemplates(client);\n\tconst { revisions, rollback } = useTemplateRevisions(client, type, locale);\n\tconst [isSaving, setIsSaving] = useState(false);\n\tconst [saveError, setSaveError] = useState<FonderieApiError | null>(null);\n\n\tconst [subject, setSubject] = useState('');\n\tconst [html, setHtml] = useState('');\n\tconst [text, setText] = useState('');\n\tconst [active, setActive] = useState(true);\n\n\tconst { preview, isPreviewing, error: previewError, renderPreview } = useTemplatePreview(client);\n\t// The sample values, as JSON the operator can edit. Text rather than an\n\t// object so a half-typed value does not have to parse on every keystroke.\n\tconst [sampleJson, setSampleJson] = useState('{}');\n\tconst [sampleError, setSampleError] = useState<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!template) return;\n\t\tsetSubject(template.subject ?? '');\n\t\tsetHtml(template.html ?? '');\n\t\tsetText(template.text);\n\t\tsetActive(template.active);\n\t}, [template]);\n\n\tconst run = useCallback(async () => {\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(sampleJson || '{}');\n\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\tsetSampleError('Sample data must be a JSON object.');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\tsetSampleError(null);\n\t\t} catch {\n\t\t\tsetSampleError('Sample data is not valid JSON.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = await renderPreview(\n\t\t\ttype,\n\t\t\t{ text, data, ...(subject ? { subject } : {}), ...(html ? { html } : {}) },\n\t\t\tlocale,\n\t\t);\n\t\t// The server reports which variables this content uses; seed the ones\n\t\t// that have no value yet with their own name, so the render shows where\n\t\t// each lands. Never overwrite something already typed.\n\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\tif (missing.length > 0) {\n\t\t\tconst seeded = { ...data, ...Object.fromEntries(missing.map((v) => [v, v])) };\n\t\t\tsetSampleJson(JSON.stringify(seeded, null, 2));\n\t\t}\n\t}, [renderPreview, type, text, subject, html, locale, sampleJson]);\n\n\t// One render once the template has loaded, so the pane is never empty on\n\t// arrival. Re-rendering after that is explicit — an edit per keystroke would\n\t// be a request per keystroke.\n\tconst loadedType = template ? `${type}:${locale ?? ''}` : null;\n\t// `run` closes over subject/html/text/sampleJson, so it is a new function on\n\t// every keystroke; depending on it here would fire a request per character\n\t// typed. Firing on the loaded template is the intent — re-rendering after\n\t// that is the Render button's job.\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: see above\n\tuseEffect(() => {\n\t\tif (loadedType) void run();\n\t}, [loadedType]);\n\n\tconst handleSubmit = async (event: FormEvent) => {\n\t\tevent.preventDefault();\n\t\tsetIsSaving(true);\n\t\tsetSaveError(null);\n\t\ttry {\n\t\t\tconst input: ISetTemplateInput = { text, active };\n\t\t\tif (subject) input.subject = subject;\n\t\t\tif (html) input.html = html;\n\t\t\tif (template?.version !== undefined) input.ifVersion = template.version;\n\t\t\tawait saveTemplate(type, input, locale);\n\t\t\t// The list hook refreshes its own list; this screen renders the single\n\t\t\t// template, so re-read it too.\n\t\t\tawait refresh();\n\t\t\tonSaved?.();\n\t\t} catch (err) {\n\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\tsetSaveError(err as FonderieApiError);\n\t\t} finally {\n\t\t\tsetIsSaving(false);\n\t\t}\n\t};\n\n\tif (isLoading) return <p style={styles.status}>Loading template…</p>;\n\tif (error)\n\t\treturn (\n\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t{error.explanation}\n\t\t\t</p>\n\t\t);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>{type}</h1>\n\t\t\t<p style={styles.meta}>\n\t\t\t\t{locale ?? 'base'} · v{template?.version ?? 1}\n\t\t\t</p>\n\n\t\t\t<div style={styles.split}>\n\t\t\t<div style={styles.column}>\n\t\t\t<form style={styles.form} onSubmit={handleSubmit}>\n\t\t\t\t<label style={styles.label} htmlFor=\"template-subject\">\n\t\t\t\t\tSubject\n\t\t\t\t</label>\n\t\t\t\t<input\n\t\t\t\t\tid=\"template-subject\"\n\t\t\t\t\tstyle={styles.input}\n\t\t\t\t\tvalue={subject}\n\t\t\t\t\tonChange={(event) => setSubject(event.target.value)}\n\t\t\t\t/>\n\n\t\t\t\t<label style={styles.label} htmlFor=\"template-html\">\n\t\t\t\t\tHTML body\n\t\t\t\t</label>\n\t\t\t\t<textarea\n\t\t\t\t\tid=\"template-html\"\n\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\tvalue={html}\n\t\t\t\t\tonChange={(event) => setHtml(event.target.value)}\n\t\t\t\t\trows={8}\n\t\t\t\t/>\n\n\t\t\t\t<label style={styles.label} htmlFor=\"template-text\">\n\t\t\t\t\tPlain-text body\n\t\t\t\t</label>\n\t\t\t\t<textarea\n\t\t\t\t\tid=\"template-text\"\n\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\tvalue={text}\n\t\t\t\t\tonChange={(event) => setText(event.target.value)}\n\t\t\t\t\trows={4}\n\t\t\t\t\trequired\n\t\t\t\t/>\n\n\t\t\t\t<label style={styles.checkboxLabel}>\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\tchecked={active}\n\t\t\t\t\t\tonChange={(event) => setActive(event.target.checked)}\n\t\t\t\t\t/>\n\t\t\t\t\tActive\n\t\t\t\t</label>\n\n\t\t\t\t{saveError && (\n\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t{saveError.explanation}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\n\t\t\t\t<button type=\"submit\" disabled={isSaving} style={styles.button}>\n\t\t\t\t\t{isSaving ? 'Saving…' : 'Save'}\n\t\t\t\t</button>\n\t\t\t</form>\n\n\t\t\t<label style={styles.label} htmlFor=\"template-sample\">\n\t\t\t\tSample data\n\t\t\t</label>\n\t\t\t<textarea\n\t\t\t\tid=\"template-sample\"\n\t\t\t\tstyle={styles.textarea}\n\t\t\t\tvalue={sampleJson}\n\t\t\t\tonChange={(event) => setSampleJson(event.target.value)}\n\t\t\t\trows={6}\n\t\t\t\tspellCheck={false}\n\t\t\t/>\n\t\t\t{sampleError && (\n\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t{sampleError}\n\t\t\t\t</p>\n\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<div style={styles.column}>\n\t\t\t\t<div style={styles.previewHeader}>\n\t\t\t\t\t<span style={styles.label}>Preview</span>\n\t\t\t\t\t<button type=\"button\" onClick={() => void run()} disabled={isPreviewing} style={styles.rollbackButton}>\n\t\t\t\t\t\t{isPreviewing ? 'Rendering…' : 'Render'}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t{previewError && (\n\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t{previewError.explanation}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t\t{preview?.subject && <p style={styles.previewSubject}>{preview.subject}</p>}\n\t\t\t\t{preview?.html ? (\n\t\t\t\t\t// sandbox=\"\" — no scripts, no same-origin. This is operator-authored\n\t\t\t\t\t// HTML and it must never execute in the dashboard's origin, which is\n\t\t\t\t\t// where the admin token lives.\n\t\t\t\t\t<iframe\n\t\t\t\t\t\ttitle=\"Template preview\"\n\t\t\t\t\t\tstyle={styles.previewFrame}\n\t\t\t\t\t\tsandbox=\"\"\n\t\t\t\t\t\tsrcDoc={preview.html}\n\t\t\t\t\t/>\n\t\t\t\t) : preview ? (\n\t\t\t\t\t<pre style={styles.previewText}>{preview.text}</pre>\n\t\t\t\t) : (\n\t\t\t\t\t<p style={styles.meta}>Nothing rendered yet.</p>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{revisions.length > 0 && (\n\t\t\t\t<div style={styles.revisions}>\n\t\t\t\t\t<h2 style={styles.subtitle}>History</h2>\n\t\t\t\t\t<ul style={styles.list}>\n\t\t\t\t\t\t{revisions.map((rev) => (\n\t\t\t\t\t\t\t<li key={rev.version} style={styles.row}>\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\tv{rev.version} — {rev.actor ?? 'unknown'} —{' '}\n\t\t\t\t\t\t\t\t\t{new Date(rev.createdAt).toLocaleString()}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => rollback(rev.version)}\n\t\t\t\t\t\t\t\t\tstyle={styles.rollbackButton}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tRoll back\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: 24, maxWidth: 1180 },\n\tsplit: { display: 'flex', gap: 24, alignItems: 'flex-start', flexWrap: 'wrap' },\n\tcolumn: { flex: '1 1 460px', minWidth: 320, display: 'flex', flexDirection: 'column' },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: 12,\n\t},\n\tpreviewSubject: { fontSize: 14, fontWeight: 600, margin: '8px 0' },\n\tpreviewFrame: {\n\t\twidth: '100%',\n\t\theight: 520,\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t},\n\tpreviewText: {\n\t\twhiteSpace: 'pre-wrap',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: 12,\n\t\tfontSize: 13,\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: { fontSize: 24, fontWeight: 700 },\n\tmeta: { fontSize: 13, color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: 16 },\n\tstatus: { padding: 24, textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: 12, fontSize: 14 },\n\tform: { display: 'flex', flexDirection: 'column', gap: 4 },\n\tlabel: { fontSize: 13, fontWeight: 600, marginTop: 12 },\n\tinput: { border: '1px solid var(--fonderie-border,#e0e0e0)', borderRadius: 8, padding: 10, fontSize: 14 },\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: 10,\n\t\tfontSize: 14,\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, fontSize: 14 },\n\tbutton: {\n\t\tmarginTop: 16,\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: 8,\n\t\tborder: 'none',\n\t\tfontSize: 14,\n\t\tfontWeight: 600,\n\t\tcursor: 'pointer',\n\t\talignSelf: 'flex-start',\n\t},\n\trevisions: { marginTop: 32 },\n\tsubtitle: { fontSize: 16, fontWeight: 600, marginBottom: 8 },\n\tlist: { listStyle: 'none', padding: 0, margin: 0 },\n\trow: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tpadding: '8px 0',\n\t\tborderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)',\n\t\tfontSize: 13,\n\t},\n\trollbackButton: {\n\t\tbackground: 'none',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: '4px 10px',\n\t\tfontSize: 12,\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/react-courier-admin';\nimport type { CSSProperties } from 'react';\n\nexport interface ITemplateListScreenProps {\n\tclient: CourierAdminClient;\n\tonSelectTemplate?: (template: ITemplateEntry) => void;\n}\n\nexport function TemplateListScreen({ client, onSelectTemplate }: ITemplateListScreenProps) {\n\tconst { templates, isLoading, error } = useTemplates(client);\n\n\tif (isLoading) return <p style={styles.status}>Loading templates…</p>;\n\tif (error)\n\t\treturn (\n\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t{error.explanation}\n\t\t\t</p>\n\t\t);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>Templates</h1>\n\t\t\t<ul style={styles.list}>\n\t\t\t\t{templates.map((template) => (\n\t\t\t\t\t<li key={`${template.type}:${template.locale ?? 'base'}`} style={styles.row}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={() => onSelectTemplate?.(template)}\n\t\t\t\t\t\t\tstyle={styles.rowButton}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span style={styles.type}>{template.type}</span>\n\t\t\t\t\t\t\t<span style={styles.locale}>{template.locale ?? 'base'}</span>\n\t\t\t\t\t\t\t<span style={template.active ? styles.active : styles.inactive}>\n\t\t\t\t\t\t\t\t{template.active ? 'Active' : 'Inactive'}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t))}\n\t\t\t</ul>\n\t\t</div>\n\t);\n}\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: 24, maxWidth: 640 },\n\ttitle: { fontSize: 24, fontWeight: 700, marginBottom: 16 },\n\tstatus: { padding: 24, textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: 12, fontSize: 14 },\n\tlist: { listStyle: 'none', padding: 0, margin: 0 },\n\trow: { borderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)' },\n\trowButton: {\n\t\twidth: '100%',\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tpadding: '12px 0',\n\t\tbackground: 'none',\n\t\tborder: 'none',\n\t\tcursor: 'pointer',\n\t\ttextAlign: 'left',\n\t},\n\ttype: { fontSize: 14, fontWeight: 600, flex: 1 },\n\tlocale: { fontSize: 13, color: 'var(--fonderie-text-muted,#5c5c5c)', marginRight: 16 },\n\tactive: { fontSize: 12, color: 'var(--fonderie-accent-strong,#009767)' },\n\tinactive: { fontSize: 12, color: 'var(--fonderie-text-muted,#5c5c5c)' },\n};\n"],"mappings":"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","names":["import_react_courier_admin","import_jsx_runtime","styles"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/screens/TemplateEditorScreen.tsx","../src/screens/TemplateListScreen.tsx"],"sourcesContent":["export type { ITemplateEditorScreenProps, ITemplateListScreenProps } from './screens';\nexport { TemplateEditorScreen, TemplateListScreen } from './screens';\n","import type { CourierAdminClient, FonderieApiError, ISetTemplateInput } from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/react-courier-admin';\nimport type { CSSProperties, FormEvent } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nconst PREVIEW_DEBOUNCE_MS = 500;\n\nexport interface ITemplateEditorScreenProps {\n\tclient: CourierAdminClient;\n\ttype: string;\n\tlocale?: string | null;\n\tonSaved?: () => void;\n}\n\nexport function TemplateEditorScreen({\n\tclient,\n\ttype,\n\tlocale,\n\tonSaved,\n}: ITemplateEditorScreenProps) {\n\tconst { template, isLoading, error, refresh } = useTemplate(client, type, locale);\n\t// Saves go through the list hook (which re-fetches the template list after\n\t// each write); mounting it adds a template-list fetch to this single-template\n\t// editor — acceptable for an admin dashboard. Its isLoading/error track that\n\t// list fetch, so the save action keeps local state.\n\tconst { saveTemplate } = useTemplates(client);\n\tconst { revisions, rollback } = useTemplateRevisions(client, type, locale);\n\tconst [isSaving, setIsSaving] = useState(false);\n\tconst [saveError, setSaveError] = useState<FonderieApiError | null>(null);\n\n\tconst [subject, setSubject] = useState('');\n\tconst [html, setHtml] = useState('');\n\tconst [text, setText] = useState('');\n\tconst [active, setActive] = useState(true);\n\n\tconst { preview, isPreviewing, error: previewError, renderPreview } = useTemplatePreview(client);\n\t// The sample values, as JSON the operator can edit. Text rather than an\n\t// object so a half-typed value does not have to parse on every keystroke.\n\tconst [sampleJson, setSampleJson] = useState('{}');\n\tconst [sampleError, setSampleError] = useState<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!template) return;\n\t\tsetSubject(template.subject ?? '');\n\t\tsetHtml(template.html ?? '');\n\t\tsetText(template.text);\n\t\tsetActive(template.active);\n\t}, [template]);\n\n\tconst run = useCallback(async () => {\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(sampleJson || '{}');\n\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\tsetSampleError('Sample data must be a JSON object.');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\tsetSampleError(null);\n\t\t} catch {\n\t\t\tsetSampleError('Sample data is not valid JSON.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = await renderPreview(\n\t\t\ttype,\n\t\t\t{ text, data, ...(subject ? { subject } : {}), ...(html ? { html } : {}) },\n\t\t\tlocale,\n\t\t);\n\t\t// The server reports which variables this content uses; seed the ones\n\t\t// that have no value yet with their own name, so the render shows where\n\t\t// each lands. Never overwrite something already typed.\n\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\tif (missing.length > 0) {\n\t\t\tconst seeded = { ...data, ...Object.fromEntries(missing.map((v) => [v, v])) };\n\t\t\tsetSampleJson(JSON.stringify(seeded, null, 2));\n\t\t}\n\t}, [renderPreview, type, text, subject, html, locale, sampleJson]);\n\n\t// The preview is always live: it renders once the template loads, then again\n\t// PREVIEW_DEBOUNCE_MS after the last edit to the content or the sample data.\n\t// Debounced, not per keystroke, so typing a paragraph costs one request.\n\t// `run` is a new function on every keystroke (it closes over the fields), so\n\t// the effect keys on the fields themselves and calls the latest `run`.\n\tconst runRef = useRef(run);\n\trunRef.current = run;\n\tconst loadedType = template ? `${type}:${locale ?? ''}` : null;\n\t// The fields ARE the trigger: each edit restarts the debounce. The effect\n\t// body reads the latest `run` through the ref, so it does not use them.\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: see above\n\tuseEffect(() => {\n\t\tif (!loadedType) return;\n\t\tconst timer = setTimeout(() => void runRef.current(), PREVIEW_DEBOUNCE_MS);\n\t\treturn () => clearTimeout(timer);\n\t}, [loadedType, subject, html, text, sampleJson]);\n\n\t// Saving what is already stored would only mint an identical version (the\n\t// server treats it as a no-op anyway), so Save waits for a real change.\n\tconst dirty =\n\t\t!!template &&\n\t\t(subject !== (template.subject ?? '') ||\n\t\t\thtml !== (template.html ?? '') ||\n\t\t\ttext !== template.text ||\n\t\t\tactive !== template.active);\n\n\tconst handleSubmit = async (event: FormEvent) => {\n\t\tevent.preventDefault();\n\t\tsetIsSaving(true);\n\t\tsetSaveError(null);\n\t\ttry {\n\t\t\tconst input: ISetTemplateInput = { text, active };\n\t\t\tif (subject) input.subject = subject;\n\t\t\tif (html) input.html = html;\n\t\t\tif (template?.version !== undefined) input.ifVersion = template.version;\n\t\t\tawait saveTemplate(type, input, locale);\n\t\t\t// The list hook refreshes its own list; this screen renders the single\n\t\t\t// template, so re-read it too.\n\t\t\tawait refresh();\n\t\t\tonSaved?.();\n\t\t} catch (err) {\n\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\tsetSaveError(err as FonderieApiError);\n\t\t} finally {\n\t\t\tsetIsSaving(false);\n\t\t}\n\t};\n\n\tif (isLoading) return <p style={styles.status}>Loading template…</p>;\n\tif (error)\n\t\treturn (\n\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t{error.explanation}\n\t\t\t</p>\n\t\t);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>{type}</h1>\n\t\t\t<p style={styles.meta}>\n\t\t\t\t{locale ?? 'base'} · v{template?.version ?? 1}\n\t\t\t</p>\n\n\t\t\t<div style={styles.split}>\n\t\t\t\t<div style={styles.column}>\n\t\t\t\t\t<form style={styles.form} onSubmit={handleSubmit}>\n\t\t\t\t\t\t<label style={styles.label} htmlFor=\"template-subject\">\n\t\t\t\t\t\t\tSubject\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tid=\"template-subject\"\n\t\t\t\t\t\t\tstyle={styles.input}\n\t\t\t\t\t\t\tvalue={subject}\n\t\t\t\t\t\t\tonChange={(event) => setSubject(event.target.value)}\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t<label style={styles.label} htmlFor=\"template-html\">\n\t\t\t\t\t\t\tHTML body\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\tid=\"template-html\"\n\t\t\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\t\t\tvalue={html}\n\t\t\t\t\t\t\tonChange={(event) => setHtml(event.target.value)}\n\t\t\t\t\t\t\trows={8}\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t<label style={styles.label} htmlFor=\"template-text\">\n\t\t\t\t\t\t\tPlain-text body\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\tid=\"template-text\"\n\t\t\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\t\tonChange={(event) => setText(event.target.value)}\n\t\t\t\t\t\t\trows={4}\n\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t<label style={styles.checkboxLabel}>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\tchecked={active}\n\t\t\t\t\t\t\t\tonChange={(event) => setActive(event.target.checked)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\tActive\n\t\t\t\t\t\t</label>\n\n\t\t\t\t\t\t{saveError && (\n\t\t\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t\t\t{saveError.explanation}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t<div style={styles.saveRow}>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\tdisabled={isSaving || !dirty}\n\t\t\t\t\t\t\t\tstyle={dirty ? styles.button : styles.buttonDisabled}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{isSaving ? 'Saving…' : 'Save'}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t{!dirty && !isSaving && <span style={styles.meta}>No changes to save</span>}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</form>\n\n\t\t\t\t\t<label style={styles.label} htmlFor=\"template-sample\">\n\t\t\t\t\t\tSample data\n\t\t\t\t\t</label>\n\t\t\t\t\t<textarea\n\t\t\t\t\t\tid=\"template-sample\"\n\t\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\t\tvalue={sampleJson}\n\t\t\t\t\t\tonChange={(event) => setSampleJson(event.target.value)}\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t/>\n\t\t\t\t\t{sampleError && (\n\t\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t\t{sampleError}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t<div style={styles.previewColumn}>\n\t\t\t\t\t<div style={styles.previewHeader}>\n\t\t\t\t\t\t<span style={styles.label}>Preview</span>\n\t\t\t\t\t\t<span style={styles.meta}>{isPreviewing ? 'Rendering…' : 'Live'}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t{previewError && (\n\t\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t\t{previewError.explanation}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)}\n\t\t\t\t\t{preview?.subject && <p style={styles.previewSubject}>{preview.subject}</p>}\n\t\t\t\t\t{preview?.html ? (\n\t\t\t\t\t\t// sandbox=\"\" — no scripts, no same-origin. This is operator-authored\n\t\t\t\t\t\t// HTML and it must never execute in the dashboard's origin, which is\n\t\t\t\t\t\t// where the admin token lives.\n\t\t\t\t\t\t<iframe\n\t\t\t\t\t\t\ttitle=\"Template preview\"\n\t\t\t\t\t\t\tstyle={styles.previewFrame}\n\t\t\t\t\t\t\tsandbox=\"\"\n\t\t\t\t\t\t\tsrcDoc={preview.html}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : preview ? (\n\t\t\t\t\t\t<pre style={styles.previewText}>{preview.text}</pre>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<p style={styles.meta}>Nothing rendered yet.</p>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{revisions.length > 0 && (\n\t\t\t\t<div style={styles.revisions}>\n\t\t\t\t\t<h2 style={styles.subtitle}>History</h2>\n\t\t\t\t\t<ul style={styles.list}>\n\t\t\t\t\t\t{revisions.map((rev) => (\n\t\t\t\t\t\t\t<li key={rev.version} style={styles.row}>\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\tv{rev.version} — {rev.actor ?? 'unknown'} —{' '}\n\t\t\t\t\t\t\t\t\t{new Date(rev.createdAt).toLocaleString()}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => rollback(rev.version)}\n\t\t\t\t\t\t\t\t\tstyle={styles.rollbackButton}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tRoll back\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '8px 40px 64px', maxWidth: 1400, boxSizing: 'border-box' },\n\t// Two columns, always: the editor on the left, the rendered result on the\n\t// right. Stacking the preview under a long form hid it below the fold.\n\tsplit: {\n\t\tdisplay: 'grid',\n\t\tgridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)',\n\t\tgap: 24,\n\t\talignItems: 'start',\n\t},\n\tcolumn: { minWidth: 0, display: 'flex', flexDirection: 'column' },\n\t// Pinned while the form scrolls, so the preview stays in view as you edit.\n\tpreviewColumn: {\n\t\tminWidth: 0,\n\t\tdisplay: 'flex',\n\t\tflexDirection: 'column',\n\t\tposition: 'sticky',\n\t\ttop: 16,\n\t},\n\tsaveRow: { display: 'flex', alignItems: 'center', gap: 12, marginTop: 16 },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: 12,\n\t},\n\tpreviewSubject: { fontSize: 14, fontWeight: 600, margin: '8px 0' },\n\tpreviewFrame: {\n\t\tboxShadow: 'var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))',\n\t\twidth: '100%',\n\t\theight: 520,\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t},\n\tpreviewText: {\n\t\twhiteSpace: 'pre-wrap',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: 12,\n\t\tfontSize: 13,\n\t\tfontFamily:\n\t\t\t'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: {\n\t\tfontSize: 22,\n\t\tfontWeight: 600,\n\t\tmargin: '8px 0 4px',\n\t\tletterSpacing: 'var(--fonderie-tracking-display,-0.05em)',\n\t\tlineHeight: 1.25,\n\t},\n\tmeta: { fontSize: 13, color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: 20 },\n\tstatus: { padding: 24, textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: {\n\t\tcolor: 'var(--fonderie-danger,#e00)',\n\t\tbackground: 'color-mix(in srgb, var(--fonderie-danger,#e00) 8%, transparent)',\n\t\tborderRadius: 6,\n\t\tpadding: '8px 12px',\n\t\tmargin: '8px 0',\n\t\tfontSize: 13.5,\n\t},\n\tform: {\n\t\tdisplay: 'flex',\n\t\tflexDirection: 'column',\n\t\tgap: 4,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 'var(--fonderie-radius-lg,8px)',\n\t\tpadding: '6px 20px 20px',\n\t\tboxShadow: 'var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))',\n\t},\n\tlabel: { fontSize: 13, fontWeight: 500, marginTop: 14, marginBottom: 2 },\n\tinput: {\n\t\theight: 36,\n\t\tboxSizing: 'border-box',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 6,\n\t\tpadding: '0 10px',\n\t\tfontSize: 13.5,\n\t\tfontFamily: 'inherit',\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t},\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 6,\n\t\tpadding: 10,\n\t\tfontSize: 13,\n\t\tlineHeight: 1.55,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t\tfontFamily:\n\t\t\t'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, fontSize: 14 },\n\tbutton: {\n\t\tdisplay: 'inline-flex',\n\t\talignItems: 'center',\n\t\theight: 36,\n\t\tboxSizing: 'border-box',\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '0 16px',\n\t\tborderRadius: 6,\n\t\tborder: '1px solid var(--fonderie-text,#171717)',\n\t\tfontSize: 13.5,\n\t\tfontWeight: 600,\n\t\tfontFamily: 'inherit',\n\t\tcursor: 'pointer',\n\t\talignSelf: 'flex-start',\n\t},\n\tbuttonDisabled: {\n\t\tdisplay: 'inline-flex',\n\t\talignItems: 'center',\n\t\theight: 36,\n\t\tboxSizing: 'border-box',\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '0 16px',\n\t\tborderRadius: 6,\n\t\tborder: '1px solid var(--fonderie-text,#171717)',\n\t\tfontSize: 13.5,\n\t\tfontWeight: 600,\n\t\tfontFamily: 'inherit',\n\t\talignSelf: 'flex-start',\n\t\topacity: 0.45,\n\t\tcursor: 'not-allowed',\n\t},\n\trevisions: { marginTop: 32 },\n\tsubtitle: {\n\t\tfontSize: 15,\n\t\tfontWeight: 600,\n\t\tmargin: '0 0 10px',\n\t\tletterSpacing: 'var(--fonderie-tracking-display,-0.05em)',\n\t},\n\tlist: {\n\t\tlistStyle: 'none',\n\t\tpadding: 0,\n\t\tmargin: 0,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 'var(--fonderie-radius-lg,8px)',\n\t\toverflow: 'hidden',\n\t},\n\trow: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tgap: 12,\n\t\tpadding: '9px 16px',\n\t\tborderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)',\n\t\tfontSize: 13,\n\t},\n\trollbackButton: {\n\t\tdisplay: 'inline-flex',\n\t\talignItems: 'center',\n\t\theight: 28,\n\t\tboxSizing: 'border-box',\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 6,\n\t\tpadding: '0 10px',\n\t\tfontSize: 12.5,\n\t\tfontWeight: 500,\n\t\tfontFamily: 'inherit',\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/react-courier-admin';\nimport type { CSSProperties } from 'react';\n\nexport interface ITemplateListScreenProps {\n\tclient: CourierAdminClient;\n\tonSelectTemplate?: (template: ITemplateEntry) => void;\n}\n\nexport function TemplateListScreen({ client, onSelectTemplate }: ITemplateListScreenProps) {\n\tconst { templates, isLoading, error } = useTemplates(client);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>Templates</h1>\n\t\t\t<p style={styles.hint}>\n\t\t\t\tEvery email the app sends. Open one to edit its copy and preview it live.\n\t\t\t</p>\n\t\t\t{isLoading ? (\n\t\t\t\t<p style={styles.status}>Loading templates…</p>\n\t\t\t) : error ? (\n\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t{error.explanation}\n\t\t\t\t</p>\n\t\t\t) : (\n\t\t\t\t<ul style={styles.list}>\n\t\t\t\t\t{templates.map((template) => (\n\t\t\t\t\t\t<li key={`${template.type}:${template.locale ?? 'base'}`} style={styles.row}>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => onSelectTemplate?.(template)}\n\t\t\t\t\t\t\t\tstyle={styles.rowButton}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span style={styles.type}>{template.type}</span>\n\t\t\t\t\t\t\t\t<span style={styles.locale}>{template.locale ?? 'default locale'}</span>\n\t\t\t\t\t\t\t\t<span style={template.active ? styles.active : styles.inactive}>\n\t\t\t\t\t\t\t\t\t<span style={styles.dot} />\n\t\t\t\t\t\t\t\t\t{template.active ? 'active' : 'inactive'}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Matches @fonderie/react-admin-screens' shell look with local values: this\n// package sits below the admin screens and cannot import their blocks.\nconst pill: CSSProperties = {\n\tdisplay: 'inline-flex',\n\talignItems: 'center',\n\tgap: 6,\n\tborderRadius: 999,\n\tpadding: '2px 9px 2px 8px',\n\tfontSize: 12,\n\tfontWeight: 600,\n\tlineHeight: 1.5,\n\tminWidth: 64,\n};\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '32px 40px 64px', maxWidth: 1160, boxSizing: 'border-box' },\n\ttitle: {\n\t\tfontSize: 22,\n\t\tfontWeight: 600,\n\t\tmargin: 0,\n\t\tletterSpacing: 'var(--fonderie-tracking-display,-0.05em)',\n\t\tlineHeight: 1.25,\n\t},\n\thint: { fontSize: 13.5, color: 'var(--fonderie-text-muted,#5c5c5c)', margin: '4px 0 24px' },\n\tstatus: { padding: '24px 0', color: 'var(--fonderie-text-muted,#5c5c5c)', fontSize: 13.5 },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: 12, fontSize: 14 },\n\tlist: {\n\t\tlistStyle: 'none',\n\t\tpadding: 0,\n\t\tmargin: 0,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 'var(--fonderie-radius-lg,8px)',\n\t\toverflow: 'hidden',\n\t\tboxShadow: 'var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))',\n\t},\n\trow: { borderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)' },\n\trowButton: {\n\t\twidth: '100%',\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tgap: 16,\n\t\tpadding: '13px 16px',\n\t\tbackground: 'none',\n\t\tborder: 'none',\n\t\tcursor: 'pointer',\n\t\ttextAlign: 'left',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t\tfontFamily: 'inherit',\n\t},\n\ttype: {\n\t\tfontSize: 13,\n\t\tfontWeight: 600,\n\t\tflex: 1,\n\t\tfontFamily:\n\t\t\t'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tlocale: { fontSize: 12.5, color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\tdot: { width: 6, height: 6, borderRadius: 999, background: 'currentColor' },\n\tactive: {\n\t\t...pill,\n\t\tcolor: 'var(--fonderie-accent-strong,#009767)',\n\t\tbackground: 'color-mix(in srgb, var(--fonderie-accent-strong,#009767) 13%, transparent)',\n\t},\n\tinactive: {\n\t\t...pill,\n\t\tcolor: 'var(--fonderie-text-muted,#5c5c5c)',\n\t\tbackground: 'color-mix(in srgb, var(--fonderie-text-muted,#5c5c5c) 10%, transparent)',\n\t},\n};\n"],"mappings":"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","names":["import_react_courier_admin","import_jsx_runtime","styles"]}
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,10 @@ import {
|
|
|
5
5
|
useTemplateRevisions,
|
|
6
6
|
useTemplates
|
|
7
7
|
} from "@fonderie/react-courier-admin";
|
|
8
|
-
import { useCallback, useEffect, useState } from "react";
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
|
|
11
|
+
var PREVIEW_DEBOUNCE_MS = 500;
|
|
11
12
|
function TemplateEditorScreen({
|
|
12
13
|
client,
|
|
13
14
|
type,
|
|
@@ -58,10 +59,15 @@ function TemplateEditorScreen({
|
|
|
58
59
|
setSampleJson(JSON.stringify(seeded, null, 2));
|
|
59
60
|
}
|
|
60
61
|
}, [renderPreview, type, text, subject, html, locale, sampleJson]);
|
|
62
|
+
const runRef = useRef(run);
|
|
63
|
+
runRef.current = run;
|
|
61
64
|
const loadedType = template ? `${type}:${locale ?? ""}` : null;
|
|
62
65
|
useEffect(() => {
|
|
63
|
-
if (loadedType)
|
|
64
|
-
|
|
66
|
+
if (!loadedType) return;
|
|
67
|
+
const timer = setTimeout(() => void runRef.current(), PREVIEW_DEBOUNCE_MS);
|
|
68
|
+
return () => clearTimeout(timer);
|
|
69
|
+
}, [loadedType, subject, html, text, sampleJson]);
|
|
70
|
+
const dirty = !!template && (subject !== (template.subject ?? "") || html !== (template.html ?? "") || text !== template.text || active !== template.active);
|
|
65
71
|
const handleSubmit = async (event) => {
|
|
66
72
|
event.preventDefault();
|
|
67
73
|
setIsSaving(true);
|
|
@@ -138,7 +144,18 @@ function TemplateEditorScreen({
|
|
|
138
144
|
"Active"
|
|
139
145
|
] }),
|
|
140
146
|
saveError && /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: saveError.explanation }),
|
|
141
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ jsxs("div", { style: styles.saveRow, children: [
|
|
148
|
+
/* @__PURE__ */ jsx(
|
|
149
|
+
"button",
|
|
150
|
+
{
|
|
151
|
+
type: "submit",
|
|
152
|
+
disabled: isSaving || !dirty,
|
|
153
|
+
style: dirty ? styles.button : styles.buttonDisabled,
|
|
154
|
+
children: isSaving ? "Saving\u2026" : "Save"
|
|
155
|
+
}
|
|
156
|
+
),
|
|
157
|
+
!dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: "No changes to save" })
|
|
158
|
+
] })
|
|
142
159
|
] }),
|
|
143
160
|
/* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-sample", children: "Sample data" }),
|
|
144
161
|
/* @__PURE__ */ jsx(
|
|
@@ -154,10 +171,10 @@ function TemplateEditorScreen({
|
|
|
154
171
|
),
|
|
155
172
|
sampleError && /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: sampleError })
|
|
156
173
|
] }),
|
|
157
|
-
/* @__PURE__ */ jsxs("div", { style: styles.
|
|
174
|
+
/* @__PURE__ */ jsxs("div", { style: styles.previewColumn, children: [
|
|
158
175
|
/* @__PURE__ */ jsxs("div", { style: styles.previewHeader, children: [
|
|
159
176
|
/* @__PURE__ */ jsx("span", { style: styles.label, children: "Preview" }),
|
|
160
|
-
/* @__PURE__ */ jsx("
|
|
177
|
+
/* @__PURE__ */ jsx("span", { style: styles.meta, children: isPreviewing ? "Rendering\u2026" : "Live" })
|
|
161
178
|
] }),
|
|
162
179
|
previewError && /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: previewError.explanation }),
|
|
163
180
|
preview?.subject && /* @__PURE__ */ jsx("p", { style: styles.previewSubject, children: preview.subject }),
|
|
@@ -203,9 +220,25 @@ function TemplateEditorScreen({
|
|
|
203
220
|
] });
|
|
204
221
|
}
|
|
205
222
|
var styles = {
|
|
206
|
-
container: { padding:
|
|
207
|
-
|
|
208
|
-
|
|
223
|
+
container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
|
|
224
|
+
// Two columns, always: the editor on the left, the rendered result on the
|
|
225
|
+
// right. Stacking the preview under a long form hid it below the fold.
|
|
226
|
+
split: {
|
|
227
|
+
display: "grid",
|
|
228
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
|
|
229
|
+
gap: 24,
|
|
230
|
+
alignItems: "start"
|
|
231
|
+
},
|
|
232
|
+
column: { minWidth: 0, display: "flex", flexDirection: "column" },
|
|
233
|
+
// Pinned while the form scrolls, so the preview stays in view as you edit.
|
|
234
|
+
previewColumn: {
|
|
235
|
+
minWidth: 0,
|
|
236
|
+
display: "flex",
|
|
237
|
+
flexDirection: "column",
|
|
238
|
+
position: "sticky",
|
|
239
|
+
top: 16
|
|
240
|
+
},
|
|
241
|
+
saveRow: { display: "flex", alignItems: "center", gap: 12, marginTop: 16 },
|
|
209
242
|
previewHeader: {
|
|
210
243
|
display: "flex",
|
|
211
244
|
justifyContent: "space-between",
|
|
@@ -214,6 +247,7 @@ var styles = {
|
|
|
214
247
|
},
|
|
215
248
|
previewSubject: { fontSize: 14, fontWeight: 600, margin: "8px 0" },
|
|
216
249
|
previewFrame: {
|
|
250
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))",
|
|
217
251
|
width: "100%",
|
|
218
252
|
height: 520,
|
|
219
253
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
@@ -228,50 +262,127 @@ var styles = {
|
|
|
228
262
|
fontSize: 13,
|
|
229
263
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
230
264
|
},
|
|
231
|
-
title: {
|
|
232
|
-
|
|
265
|
+
title: {
|
|
266
|
+
fontSize: 22,
|
|
267
|
+
fontWeight: 600,
|
|
268
|
+
margin: "8px 0 4px",
|
|
269
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)",
|
|
270
|
+
lineHeight: 1.25
|
|
271
|
+
},
|
|
272
|
+
meta: { fontSize: 13, color: "var(--fonderie-text-muted,#5c5c5c)", marginBottom: 20 },
|
|
233
273
|
status: { padding: 24, textAlign: "center", color: "var(--fonderie-text-muted,#5c5c5c)" },
|
|
234
|
-
error: {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
274
|
+
error: {
|
|
275
|
+
color: "var(--fonderie-danger,#e00)",
|
|
276
|
+
background: "color-mix(in srgb, var(--fonderie-danger,#e00) 8%, transparent)",
|
|
277
|
+
borderRadius: 6,
|
|
278
|
+
padding: "8px 12px",
|
|
279
|
+
margin: "8px 0",
|
|
280
|
+
fontSize: 13.5
|
|
281
|
+
},
|
|
282
|
+
form: {
|
|
283
|
+
display: "flex",
|
|
284
|
+
flexDirection: "column",
|
|
285
|
+
gap: 4,
|
|
286
|
+
background: "var(--fonderie-surface,#fff)",
|
|
287
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
288
|
+
borderRadius: "var(--fonderie-radius-lg,8px)",
|
|
289
|
+
padding: "6px 20px 20px",
|
|
290
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
|
|
291
|
+
},
|
|
292
|
+
label: { fontSize: 13, fontWeight: 500, marginTop: 14, marginBottom: 2 },
|
|
293
|
+
input: {
|
|
294
|
+
height: 36,
|
|
295
|
+
boxSizing: "border-box",
|
|
296
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
297
|
+
borderRadius: 6,
|
|
298
|
+
padding: "0 10px",
|
|
299
|
+
fontSize: 13.5,
|
|
300
|
+
fontFamily: "inherit",
|
|
301
|
+
background: "var(--fonderie-surface,#fff)",
|
|
302
|
+
color: "var(--fonderie-text,#171717)"
|
|
303
|
+
},
|
|
238
304
|
textarea: {
|
|
239
305
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
240
|
-
borderRadius:
|
|
306
|
+
borderRadius: 6,
|
|
241
307
|
padding: 10,
|
|
242
|
-
fontSize:
|
|
308
|
+
fontSize: 13,
|
|
309
|
+
lineHeight: 1.55,
|
|
310
|
+
background: "var(--fonderie-surface,#fff)",
|
|
311
|
+
color: "var(--fonderie-text,#171717)",
|
|
243
312
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
244
313
|
},
|
|
245
314
|
checkboxLabel: { display: "flex", alignItems: "center", gap: 8, marginTop: 12, fontSize: 14 },
|
|
246
315
|
button: {
|
|
247
|
-
|
|
316
|
+
display: "inline-flex",
|
|
317
|
+
alignItems: "center",
|
|
318
|
+
height: 36,
|
|
319
|
+
boxSizing: "border-box",
|
|
248
320
|
backgroundColor: "var(--fonderie-text,#171717)",
|
|
249
321
|
color: "var(--fonderie-surface,#fff)",
|
|
250
|
-
padding: "
|
|
251
|
-
borderRadius:
|
|
252
|
-
border: "
|
|
253
|
-
fontSize:
|
|
322
|
+
padding: "0 16px",
|
|
323
|
+
borderRadius: 6,
|
|
324
|
+
border: "1px solid var(--fonderie-text,#171717)",
|
|
325
|
+
fontSize: 13.5,
|
|
254
326
|
fontWeight: 600,
|
|
327
|
+
fontFamily: "inherit",
|
|
255
328
|
cursor: "pointer",
|
|
256
329
|
alignSelf: "flex-start"
|
|
257
330
|
},
|
|
331
|
+
buttonDisabled: {
|
|
332
|
+
display: "inline-flex",
|
|
333
|
+
alignItems: "center",
|
|
334
|
+
height: 36,
|
|
335
|
+
boxSizing: "border-box",
|
|
336
|
+
backgroundColor: "var(--fonderie-text,#171717)",
|
|
337
|
+
color: "var(--fonderie-surface,#fff)",
|
|
338
|
+
padding: "0 16px",
|
|
339
|
+
borderRadius: 6,
|
|
340
|
+
border: "1px solid var(--fonderie-text,#171717)",
|
|
341
|
+
fontSize: 13.5,
|
|
342
|
+
fontWeight: 600,
|
|
343
|
+
fontFamily: "inherit",
|
|
344
|
+
alignSelf: "flex-start",
|
|
345
|
+
opacity: 0.45,
|
|
346
|
+
cursor: "not-allowed"
|
|
347
|
+
},
|
|
258
348
|
revisions: { marginTop: 32 },
|
|
259
|
-
subtitle: {
|
|
260
|
-
|
|
349
|
+
subtitle: {
|
|
350
|
+
fontSize: 15,
|
|
351
|
+
fontWeight: 600,
|
|
352
|
+
margin: "0 0 10px",
|
|
353
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
|
|
354
|
+
},
|
|
355
|
+
list: {
|
|
356
|
+
listStyle: "none",
|
|
357
|
+
padding: 0,
|
|
358
|
+
margin: 0,
|
|
359
|
+
background: "var(--fonderie-surface,#fff)",
|
|
360
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
361
|
+
borderRadius: "var(--fonderie-radius-lg,8px)",
|
|
362
|
+
overflow: "hidden"
|
|
363
|
+
},
|
|
261
364
|
row: {
|
|
262
365
|
display: "flex",
|
|
263
366
|
justifyContent: "space-between",
|
|
264
367
|
alignItems: "center",
|
|
265
|
-
|
|
368
|
+
gap: 12,
|
|
369
|
+
padding: "9px 16px",
|
|
266
370
|
borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)",
|
|
267
371
|
fontSize: 13
|
|
268
372
|
},
|
|
269
373
|
rollbackButton: {
|
|
270
|
-
|
|
374
|
+
display: "inline-flex",
|
|
375
|
+
alignItems: "center",
|
|
376
|
+
height: 28,
|
|
377
|
+
boxSizing: "border-box",
|
|
378
|
+
background: "var(--fonderie-surface,#fff)",
|
|
379
|
+
color: "var(--fonderie-text,#171717)",
|
|
271
380
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
272
|
-
borderRadius:
|
|
273
|
-
padding: "
|
|
274
|
-
fontSize: 12,
|
|
381
|
+
borderRadius: 6,
|
|
382
|
+
padding: "0 10px",
|
|
383
|
+
fontSize: 12.5,
|
|
384
|
+
fontWeight: 500,
|
|
385
|
+
fontFamily: "inherit",
|
|
275
386
|
cursor: "pointer"
|
|
276
387
|
}
|
|
277
388
|
};
|
|
@@ -281,12 +392,10 @@ import { useTemplates as useTemplates2 } from "@fonderie/react-courier-admin";
|
|
|
281
392
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
282
393
|
function TemplateListScreen({ client, onSelectTemplate }) {
|
|
283
394
|
const { templates, isLoading, error } = useTemplates2(client);
|
|
284
|
-
if (isLoading) return /* @__PURE__ */ jsx2("p", { style: styles2.status, children: "Loading templates\u2026" });
|
|
285
|
-
if (error)
|
|
286
|
-
return /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation });
|
|
287
395
|
return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
|
|
288
396
|
/* @__PURE__ */ jsx2("h1", { style: styles2.title, children: "Templates" }),
|
|
289
|
-
/* @__PURE__ */ jsx2("
|
|
397
|
+
/* @__PURE__ */ jsx2("p", { style: styles2.hint, children: "Every email the app sends. Open one to edit its copy and preview it live." }),
|
|
398
|
+
isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: "Loading templates\u2026" }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsx2("ul", { style: styles2.list, children: templates.map((template) => /* @__PURE__ */ jsx2("li", { style: styles2.row, children: /* @__PURE__ */ jsxs2(
|
|
290
399
|
"button",
|
|
291
400
|
{
|
|
292
401
|
type: "button",
|
|
@@ -294,35 +403,82 @@ function TemplateListScreen({ client, onSelectTemplate }) {
|
|
|
294
403
|
style: styles2.rowButton,
|
|
295
404
|
children: [
|
|
296
405
|
/* @__PURE__ */ jsx2("span", { style: styles2.type, children: template.type }),
|
|
297
|
-
/* @__PURE__ */ jsx2("span", { style: styles2.locale, children: template.locale ?? "
|
|
298
|
-
/* @__PURE__ */
|
|
406
|
+
/* @__PURE__ */ jsx2("span", { style: styles2.locale, children: template.locale ?? "default locale" }),
|
|
407
|
+
/* @__PURE__ */ jsxs2("span", { style: template.active ? styles2.active : styles2.inactive, children: [
|
|
408
|
+
/* @__PURE__ */ jsx2("span", { style: styles2.dot }),
|
|
409
|
+
template.active ? "active" : "inactive"
|
|
410
|
+
] })
|
|
299
411
|
]
|
|
300
412
|
}
|
|
301
413
|
) }, `${template.type}:${template.locale ?? "base"}`)) })
|
|
302
414
|
] });
|
|
303
415
|
}
|
|
416
|
+
var pill = {
|
|
417
|
+
display: "inline-flex",
|
|
418
|
+
alignItems: "center",
|
|
419
|
+
gap: 6,
|
|
420
|
+
borderRadius: 999,
|
|
421
|
+
padding: "2px 9px 2px 8px",
|
|
422
|
+
fontSize: 12,
|
|
423
|
+
fontWeight: 600,
|
|
424
|
+
lineHeight: 1.5,
|
|
425
|
+
minWidth: 64
|
|
426
|
+
};
|
|
304
427
|
var styles2 = {
|
|
305
|
-
container: { padding:
|
|
306
|
-
title: {
|
|
307
|
-
|
|
428
|
+
container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
|
|
429
|
+
title: {
|
|
430
|
+
fontSize: 22,
|
|
431
|
+
fontWeight: 600,
|
|
432
|
+
margin: 0,
|
|
433
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)",
|
|
434
|
+
lineHeight: 1.25
|
|
435
|
+
},
|
|
436
|
+
hint: { fontSize: 13.5, color: "var(--fonderie-text-muted,#5c5c5c)", margin: "4px 0 24px" },
|
|
437
|
+
status: { padding: "24px 0", color: "var(--fonderie-text-muted,#5c5c5c)", fontSize: 13.5 },
|
|
308
438
|
error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
|
|
309
|
-
list: {
|
|
439
|
+
list: {
|
|
440
|
+
listStyle: "none",
|
|
441
|
+
padding: 0,
|
|
442
|
+
margin: 0,
|
|
443
|
+
background: "var(--fonderie-surface,#fff)",
|
|
444
|
+
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
445
|
+
borderRadius: "var(--fonderie-radius-lg,8px)",
|
|
446
|
+
overflow: "hidden",
|
|
447
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
|
|
448
|
+
},
|
|
310
449
|
row: { borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)" },
|
|
311
450
|
rowButton: {
|
|
312
451
|
width: "100%",
|
|
313
452
|
display: "flex",
|
|
314
453
|
justifyContent: "space-between",
|
|
315
454
|
alignItems: "center",
|
|
316
|
-
|
|
455
|
+
gap: 16,
|
|
456
|
+
padding: "13px 16px",
|
|
317
457
|
background: "none",
|
|
318
458
|
border: "none",
|
|
319
459
|
cursor: "pointer",
|
|
320
|
-
textAlign: "left"
|
|
460
|
+
textAlign: "left",
|
|
461
|
+
color: "var(--fonderie-text,#171717)",
|
|
462
|
+
fontFamily: "inherit"
|
|
321
463
|
},
|
|
322
|
-
type: {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
464
|
+
type: {
|
|
465
|
+
fontSize: 13,
|
|
466
|
+
fontWeight: 600,
|
|
467
|
+
flex: 1,
|
|
468
|
+
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
469
|
+
},
|
|
470
|
+
locale: { fontSize: 12.5, color: "var(--fonderie-text-muted,#5c5c5c)" },
|
|
471
|
+
dot: { width: 6, height: 6, borderRadius: 999, background: "currentColor" },
|
|
472
|
+
active: {
|
|
473
|
+
...pill,
|
|
474
|
+
color: "var(--fonderie-accent-strong,#009767)",
|
|
475
|
+
background: "color-mix(in srgb, var(--fonderie-accent-strong,#009767) 13%, transparent)"
|
|
476
|
+
},
|
|
477
|
+
inactive: {
|
|
478
|
+
...pill,
|
|
479
|
+
color: "var(--fonderie-text-muted,#5c5c5c)",
|
|
480
|
+
background: "color-mix(in srgb, var(--fonderie-text-muted,#5c5c5c) 10%, transparent)"
|
|
481
|
+
}
|
|
326
482
|
};
|
|
327
483
|
export {
|
|
328
484
|
TemplateEditorScreen,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/screens/TemplateEditorScreen.tsx","../src/screens/TemplateListScreen.tsx"],"sourcesContent":["import type { CourierAdminClient, FonderieApiError, ISetTemplateInput } from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/react-courier-admin';\nimport type { CSSProperties, FormEvent } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nexport interface ITemplateEditorScreenProps {\n\tclient: CourierAdminClient;\n\ttype: string;\n\tlocale?: string | null;\n\tonSaved?: () => void;\n}\n\nexport function TemplateEditorScreen({\n\tclient,\n\ttype,\n\tlocale,\n\tonSaved,\n}: ITemplateEditorScreenProps) {\n\tconst { template, isLoading, error, refresh } = useTemplate(client, type, locale);\n\t// Saves go through the list hook (which re-fetches the template list after\n\t// each write); mounting it adds a template-list fetch to this single-template\n\t// editor — acceptable for an admin dashboard. Its isLoading/error track that\n\t// list fetch, so the save action keeps local state.\n\tconst { saveTemplate } = useTemplates(client);\n\tconst { revisions, rollback } = useTemplateRevisions(client, type, locale);\n\tconst [isSaving, setIsSaving] = useState(false);\n\tconst [saveError, setSaveError] = useState<FonderieApiError | null>(null);\n\n\tconst [subject, setSubject] = useState('');\n\tconst [html, setHtml] = useState('');\n\tconst [text, setText] = useState('');\n\tconst [active, setActive] = useState(true);\n\n\tconst { preview, isPreviewing, error: previewError, renderPreview } = useTemplatePreview(client);\n\t// The sample values, as JSON the operator can edit. Text rather than an\n\t// object so a half-typed value does not have to parse on every keystroke.\n\tconst [sampleJson, setSampleJson] = useState('{}');\n\tconst [sampleError, setSampleError] = useState<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!template) return;\n\t\tsetSubject(template.subject ?? '');\n\t\tsetHtml(template.html ?? '');\n\t\tsetText(template.text);\n\t\tsetActive(template.active);\n\t}, [template]);\n\n\tconst run = useCallback(async () => {\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(sampleJson || '{}');\n\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\tsetSampleError('Sample data must be a JSON object.');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\tsetSampleError(null);\n\t\t} catch {\n\t\t\tsetSampleError('Sample data is not valid JSON.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = await renderPreview(\n\t\t\ttype,\n\t\t\t{ text, data, ...(subject ? { subject } : {}), ...(html ? { html } : {}) },\n\t\t\tlocale,\n\t\t);\n\t\t// The server reports which variables this content uses; seed the ones\n\t\t// that have no value yet with their own name, so the render shows where\n\t\t// each lands. Never overwrite something already typed.\n\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\tif (missing.length > 0) {\n\t\t\tconst seeded = { ...data, ...Object.fromEntries(missing.map((v) => [v, v])) };\n\t\t\tsetSampleJson(JSON.stringify(seeded, null, 2));\n\t\t}\n\t}, [renderPreview, type, text, subject, html, locale, sampleJson]);\n\n\t// One render once the template has loaded, so the pane is never empty on\n\t// arrival. Re-rendering after that is explicit — an edit per keystroke would\n\t// be a request per keystroke.\n\tconst loadedType = template ? `${type}:${locale ?? ''}` : null;\n\t// `run` closes over subject/html/text/sampleJson, so it is a new function on\n\t// every keystroke; depending on it here would fire a request per character\n\t// typed. Firing on the loaded template is the intent — re-rendering after\n\t// that is the Render button's job.\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: see above\n\tuseEffect(() => {\n\t\tif (loadedType) void run();\n\t}, [loadedType]);\n\n\tconst handleSubmit = async (event: FormEvent) => {\n\t\tevent.preventDefault();\n\t\tsetIsSaving(true);\n\t\tsetSaveError(null);\n\t\ttry {\n\t\t\tconst input: ISetTemplateInput = { text, active };\n\t\t\tif (subject) input.subject = subject;\n\t\t\tif (html) input.html = html;\n\t\t\tif (template?.version !== undefined) input.ifVersion = template.version;\n\t\t\tawait saveTemplate(type, input, locale);\n\t\t\t// The list hook refreshes its own list; this screen renders the single\n\t\t\t// template, so re-read it too.\n\t\t\tawait refresh();\n\t\t\tonSaved?.();\n\t\t} catch (err) {\n\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\tsetSaveError(err as FonderieApiError);\n\t\t} finally {\n\t\t\tsetIsSaving(false);\n\t\t}\n\t};\n\n\tif (isLoading) return <p style={styles.status}>Loading template…</p>;\n\tif (error)\n\t\treturn (\n\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t{error.explanation}\n\t\t\t</p>\n\t\t);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>{type}</h1>\n\t\t\t<p style={styles.meta}>\n\t\t\t\t{locale ?? 'base'} · v{template?.version ?? 1}\n\t\t\t</p>\n\n\t\t\t<div style={styles.split}>\n\t\t\t<div style={styles.column}>\n\t\t\t<form style={styles.form} onSubmit={handleSubmit}>\n\t\t\t\t<label style={styles.label} htmlFor=\"template-subject\">\n\t\t\t\t\tSubject\n\t\t\t\t</label>\n\t\t\t\t<input\n\t\t\t\t\tid=\"template-subject\"\n\t\t\t\t\tstyle={styles.input}\n\t\t\t\t\tvalue={subject}\n\t\t\t\t\tonChange={(event) => setSubject(event.target.value)}\n\t\t\t\t/>\n\n\t\t\t\t<label style={styles.label} htmlFor=\"template-html\">\n\t\t\t\t\tHTML body\n\t\t\t\t</label>\n\t\t\t\t<textarea\n\t\t\t\t\tid=\"template-html\"\n\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\tvalue={html}\n\t\t\t\t\tonChange={(event) => setHtml(event.target.value)}\n\t\t\t\t\trows={8}\n\t\t\t\t/>\n\n\t\t\t\t<label style={styles.label} htmlFor=\"template-text\">\n\t\t\t\t\tPlain-text body\n\t\t\t\t</label>\n\t\t\t\t<textarea\n\t\t\t\t\tid=\"template-text\"\n\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\tvalue={text}\n\t\t\t\t\tonChange={(event) => setText(event.target.value)}\n\t\t\t\t\trows={4}\n\t\t\t\t\trequired\n\t\t\t\t/>\n\n\t\t\t\t<label style={styles.checkboxLabel}>\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\tchecked={active}\n\t\t\t\t\t\tonChange={(event) => setActive(event.target.checked)}\n\t\t\t\t\t/>\n\t\t\t\t\tActive\n\t\t\t\t</label>\n\n\t\t\t\t{saveError && (\n\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t{saveError.explanation}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\n\t\t\t\t<button type=\"submit\" disabled={isSaving} style={styles.button}>\n\t\t\t\t\t{isSaving ? 'Saving…' : 'Save'}\n\t\t\t\t</button>\n\t\t\t</form>\n\n\t\t\t<label style={styles.label} htmlFor=\"template-sample\">\n\t\t\t\tSample data\n\t\t\t</label>\n\t\t\t<textarea\n\t\t\t\tid=\"template-sample\"\n\t\t\t\tstyle={styles.textarea}\n\t\t\t\tvalue={sampleJson}\n\t\t\t\tonChange={(event) => setSampleJson(event.target.value)}\n\t\t\t\trows={6}\n\t\t\t\tspellCheck={false}\n\t\t\t/>\n\t\t\t{sampleError && (\n\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t{sampleError}\n\t\t\t\t</p>\n\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<div style={styles.column}>\n\t\t\t\t<div style={styles.previewHeader}>\n\t\t\t\t\t<span style={styles.label}>Preview</span>\n\t\t\t\t\t<button type=\"button\" onClick={() => void run()} disabled={isPreviewing} style={styles.rollbackButton}>\n\t\t\t\t\t\t{isPreviewing ? 'Rendering…' : 'Render'}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t{previewError && (\n\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t{previewError.explanation}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t\t{preview?.subject && <p style={styles.previewSubject}>{preview.subject}</p>}\n\t\t\t\t{preview?.html ? (\n\t\t\t\t\t// sandbox=\"\" — no scripts, no same-origin. This is operator-authored\n\t\t\t\t\t// HTML and it must never execute in the dashboard's origin, which is\n\t\t\t\t\t// where the admin token lives.\n\t\t\t\t\t<iframe\n\t\t\t\t\t\ttitle=\"Template preview\"\n\t\t\t\t\t\tstyle={styles.previewFrame}\n\t\t\t\t\t\tsandbox=\"\"\n\t\t\t\t\t\tsrcDoc={preview.html}\n\t\t\t\t\t/>\n\t\t\t\t) : preview ? (\n\t\t\t\t\t<pre style={styles.previewText}>{preview.text}</pre>\n\t\t\t\t) : (\n\t\t\t\t\t<p style={styles.meta}>Nothing rendered yet.</p>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{revisions.length > 0 && (\n\t\t\t\t<div style={styles.revisions}>\n\t\t\t\t\t<h2 style={styles.subtitle}>History</h2>\n\t\t\t\t\t<ul style={styles.list}>\n\t\t\t\t\t\t{revisions.map((rev) => (\n\t\t\t\t\t\t\t<li key={rev.version} style={styles.row}>\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\tv{rev.version} — {rev.actor ?? 'unknown'} —{' '}\n\t\t\t\t\t\t\t\t\t{new Date(rev.createdAt).toLocaleString()}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => rollback(rev.version)}\n\t\t\t\t\t\t\t\t\tstyle={styles.rollbackButton}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tRoll back\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: 24, maxWidth: 1180 },\n\tsplit: { display: 'flex', gap: 24, alignItems: 'flex-start', flexWrap: 'wrap' },\n\tcolumn: { flex: '1 1 460px', minWidth: 320, display: 'flex', flexDirection: 'column' },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: 12,\n\t},\n\tpreviewSubject: { fontSize: 14, fontWeight: 600, margin: '8px 0' },\n\tpreviewFrame: {\n\t\twidth: '100%',\n\t\theight: 520,\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t},\n\tpreviewText: {\n\t\twhiteSpace: 'pre-wrap',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: 12,\n\t\tfontSize: 13,\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: { fontSize: 24, fontWeight: 700 },\n\tmeta: { fontSize: 13, color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: 16 },\n\tstatus: { padding: 24, textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: 12, fontSize: 14 },\n\tform: { display: 'flex', flexDirection: 'column', gap: 4 },\n\tlabel: { fontSize: 13, fontWeight: 600, marginTop: 12 },\n\tinput: { border: '1px solid var(--fonderie-border,#e0e0e0)', borderRadius: 8, padding: 10, fontSize: 14 },\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: 10,\n\t\tfontSize: 14,\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, fontSize: 14 },\n\tbutton: {\n\t\tmarginTop: 16,\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: 8,\n\t\tborder: 'none',\n\t\tfontSize: 14,\n\t\tfontWeight: 600,\n\t\tcursor: 'pointer',\n\t\talignSelf: 'flex-start',\n\t},\n\trevisions: { marginTop: 32 },\n\tsubtitle: { fontSize: 16, fontWeight: 600, marginBottom: 8 },\n\tlist: { listStyle: 'none', padding: 0, margin: 0 },\n\trow: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tpadding: '8px 0',\n\t\tborderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)',\n\t\tfontSize: 13,\n\t},\n\trollbackButton: {\n\t\tbackground: 'none',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: '4px 10px',\n\t\tfontSize: 12,\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/react-courier-admin';\nimport type { CSSProperties } from 'react';\n\nexport interface ITemplateListScreenProps {\n\tclient: CourierAdminClient;\n\tonSelectTemplate?: (template: ITemplateEntry) => void;\n}\n\nexport function TemplateListScreen({ client, onSelectTemplate }: ITemplateListScreenProps) {\n\tconst { templates, isLoading, error } = useTemplates(client);\n\n\tif (isLoading) return <p style={styles.status}>Loading templates…</p>;\n\tif (error)\n\t\treturn (\n\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t{error.explanation}\n\t\t\t</p>\n\t\t);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>Templates</h1>\n\t\t\t<ul style={styles.list}>\n\t\t\t\t{templates.map((template) => (\n\t\t\t\t\t<li key={`${template.type}:${template.locale ?? 'base'}`} style={styles.row}>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={() => onSelectTemplate?.(template)}\n\t\t\t\t\t\t\tstyle={styles.rowButton}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span style={styles.type}>{template.type}</span>\n\t\t\t\t\t\t\t<span style={styles.locale}>{template.locale ?? 'base'}</span>\n\t\t\t\t\t\t\t<span style={template.active ? styles.active : styles.inactive}>\n\t\t\t\t\t\t\t\t{template.active ? 'Active' : 'Inactive'}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t))}\n\t\t\t</ul>\n\t\t</div>\n\t);\n}\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: 24, maxWidth: 640 },\n\ttitle: { fontSize: 24, fontWeight: 700, marginBottom: 16 },\n\tstatus: { padding: 24, textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: 12, fontSize: 14 },\n\tlist: { listStyle: 'none', padding: 0, margin: 0 },\n\trow: { borderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)' },\n\trowButton: {\n\t\twidth: '100%',\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tpadding: '12px 0',\n\t\tbackground: 'none',\n\t\tborder: 'none',\n\t\tcursor: 'pointer',\n\t\ttextAlign: 'left',\n\t},\n\ttype: { fontSize: 14, fontWeight: 600, flex: 1 },\n\tlocale: { fontSize: 13, color: 'var(--fonderie-text-muted,#5c5c5c)', marginRight: 16 },\n\tactive: { fontSize: 12, color: 'var(--fonderie-accent-strong,#009767)' },\n\tinactive: { fontSize: 12, color: 'var(--fonderie-text-muted,#5c5c5c)' },\n};\n"],"mappings":"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","names":["useTemplates","jsx","jsxs","styles"]}
|
|
1
|
+
{"version":3,"sources":["../src/screens/TemplateEditorScreen.tsx","../src/screens/TemplateListScreen.tsx"],"sourcesContent":["import type { CourierAdminClient, FonderieApiError, ISetTemplateInput } from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/react-courier-admin';\nimport type { CSSProperties, FormEvent } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nconst PREVIEW_DEBOUNCE_MS = 500;\n\nexport interface ITemplateEditorScreenProps {\n\tclient: CourierAdminClient;\n\ttype: string;\n\tlocale?: string | null;\n\tonSaved?: () => void;\n}\n\nexport function TemplateEditorScreen({\n\tclient,\n\ttype,\n\tlocale,\n\tonSaved,\n}: ITemplateEditorScreenProps) {\n\tconst { template, isLoading, error, refresh } = useTemplate(client, type, locale);\n\t// Saves go through the list hook (which re-fetches the template list after\n\t// each write); mounting it adds a template-list fetch to this single-template\n\t// editor — acceptable for an admin dashboard. Its isLoading/error track that\n\t// list fetch, so the save action keeps local state.\n\tconst { saveTemplate } = useTemplates(client);\n\tconst { revisions, rollback } = useTemplateRevisions(client, type, locale);\n\tconst [isSaving, setIsSaving] = useState(false);\n\tconst [saveError, setSaveError] = useState<FonderieApiError | null>(null);\n\n\tconst [subject, setSubject] = useState('');\n\tconst [html, setHtml] = useState('');\n\tconst [text, setText] = useState('');\n\tconst [active, setActive] = useState(true);\n\n\tconst { preview, isPreviewing, error: previewError, renderPreview } = useTemplatePreview(client);\n\t// The sample values, as JSON the operator can edit. Text rather than an\n\t// object so a half-typed value does not have to parse on every keystroke.\n\tconst [sampleJson, setSampleJson] = useState('{}');\n\tconst [sampleError, setSampleError] = useState<string | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!template) return;\n\t\tsetSubject(template.subject ?? '');\n\t\tsetHtml(template.html ?? '');\n\t\tsetText(template.text);\n\t\tsetActive(template.active);\n\t}, [template]);\n\n\tconst run = useCallback(async () => {\n\t\tlet data: Record<string, unknown> = {};\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(sampleJson || '{}');\n\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\tsetSampleError('Sample data must be a JSON object.');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\tsetSampleError(null);\n\t\t} catch {\n\t\t\tsetSampleError('Sample data is not valid JSON.');\n\t\t\treturn;\n\t\t}\n\t\tconst result = await renderPreview(\n\t\t\ttype,\n\t\t\t{ text, data, ...(subject ? { subject } : {}), ...(html ? { html } : {}) },\n\t\t\tlocale,\n\t\t);\n\t\t// The server reports which variables this content uses; seed the ones\n\t\t// that have no value yet with their own name, so the render shows where\n\t\t// each lands. Never overwrite something already typed.\n\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\tif (missing.length > 0) {\n\t\t\tconst seeded = { ...data, ...Object.fromEntries(missing.map((v) => [v, v])) };\n\t\t\tsetSampleJson(JSON.stringify(seeded, null, 2));\n\t\t}\n\t}, [renderPreview, type, text, subject, html, locale, sampleJson]);\n\n\t// The preview is always live: it renders once the template loads, then again\n\t// PREVIEW_DEBOUNCE_MS after the last edit to the content or the sample data.\n\t// Debounced, not per keystroke, so typing a paragraph costs one request.\n\t// `run` is a new function on every keystroke (it closes over the fields), so\n\t// the effect keys on the fields themselves and calls the latest `run`.\n\tconst runRef = useRef(run);\n\trunRef.current = run;\n\tconst loadedType = template ? `${type}:${locale ?? ''}` : null;\n\t// The fields ARE the trigger: each edit restarts the debounce. The effect\n\t// body reads the latest `run` through the ref, so it does not use them.\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: see above\n\tuseEffect(() => {\n\t\tif (!loadedType) return;\n\t\tconst timer = setTimeout(() => void runRef.current(), PREVIEW_DEBOUNCE_MS);\n\t\treturn () => clearTimeout(timer);\n\t}, [loadedType, subject, html, text, sampleJson]);\n\n\t// Saving what is already stored would only mint an identical version (the\n\t// server treats it as a no-op anyway), so Save waits for a real change.\n\tconst dirty =\n\t\t!!template &&\n\t\t(subject !== (template.subject ?? '') ||\n\t\t\thtml !== (template.html ?? '') ||\n\t\t\ttext !== template.text ||\n\t\t\tactive !== template.active);\n\n\tconst handleSubmit = async (event: FormEvent) => {\n\t\tevent.preventDefault();\n\t\tsetIsSaving(true);\n\t\tsetSaveError(null);\n\t\ttry {\n\t\t\tconst input: ISetTemplateInput = { text, active };\n\t\t\tif (subject) input.subject = subject;\n\t\t\tif (html) input.html = html;\n\t\t\tif (template?.version !== undefined) input.ifVersion = template.version;\n\t\t\tawait saveTemplate(type, input, locale);\n\t\t\t// The list hook refreshes its own list; this screen renders the single\n\t\t\t// template, so re-read it too.\n\t\t\tawait refresh();\n\t\t\tonSaved?.();\n\t\t} catch (err) {\n\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\tsetSaveError(err as FonderieApiError);\n\t\t} finally {\n\t\t\tsetIsSaving(false);\n\t\t}\n\t};\n\n\tif (isLoading) return <p style={styles.status}>Loading template…</p>;\n\tif (error)\n\t\treturn (\n\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t{error.explanation}\n\t\t\t</p>\n\t\t);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>{type}</h1>\n\t\t\t<p style={styles.meta}>\n\t\t\t\t{locale ?? 'base'} · v{template?.version ?? 1}\n\t\t\t</p>\n\n\t\t\t<div style={styles.split}>\n\t\t\t\t<div style={styles.column}>\n\t\t\t\t\t<form style={styles.form} onSubmit={handleSubmit}>\n\t\t\t\t\t\t<label style={styles.label} htmlFor=\"template-subject\">\n\t\t\t\t\t\t\tSubject\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tid=\"template-subject\"\n\t\t\t\t\t\t\tstyle={styles.input}\n\t\t\t\t\t\t\tvalue={subject}\n\t\t\t\t\t\t\tonChange={(event) => setSubject(event.target.value)}\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t<label style={styles.label} htmlFor=\"template-html\">\n\t\t\t\t\t\t\tHTML body\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\tid=\"template-html\"\n\t\t\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\t\t\tvalue={html}\n\t\t\t\t\t\t\tonChange={(event) => setHtml(event.target.value)}\n\t\t\t\t\t\t\trows={8}\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t<label style={styles.label} htmlFor=\"template-text\">\n\t\t\t\t\t\t\tPlain-text body\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<textarea\n\t\t\t\t\t\t\tid=\"template-text\"\n\t\t\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\t\t\tvalue={text}\n\t\t\t\t\t\t\tonChange={(event) => setText(event.target.value)}\n\t\t\t\t\t\t\trows={4}\n\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t<label style={styles.checkboxLabel}>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\tchecked={active}\n\t\t\t\t\t\t\t\tonChange={(event) => setActive(event.target.checked)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\tActive\n\t\t\t\t\t\t</label>\n\n\t\t\t\t\t\t{saveError && (\n\t\t\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t\t\t{saveError.explanation}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t<div style={styles.saveRow}>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\tdisabled={isSaving || !dirty}\n\t\t\t\t\t\t\t\tstyle={dirty ? styles.button : styles.buttonDisabled}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{isSaving ? 'Saving…' : 'Save'}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t{!dirty && !isSaving && <span style={styles.meta}>No changes to save</span>}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</form>\n\n\t\t\t\t\t<label style={styles.label} htmlFor=\"template-sample\">\n\t\t\t\t\t\tSample data\n\t\t\t\t\t</label>\n\t\t\t\t\t<textarea\n\t\t\t\t\t\tid=\"template-sample\"\n\t\t\t\t\t\tstyle={styles.textarea}\n\t\t\t\t\t\tvalue={sampleJson}\n\t\t\t\t\t\tonChange={(event) => setSampleJson(event.target.value)}\n\t\t\t\t\t\trows={6}\n\t\t\t\t\t\tspellCheck={false}\n\t\t\t\t\t/>\n\t\t\t\t\t{sampleError && (\n\t\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t\t{sampleError}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t<div style={styles.previewColumn}>\n\t\t\t\t\t<div style={styles.previewHeader}>\n\t\t\t\t\t\t<span style={styles.label}>Preview</span>\n\t\t\t\t\t\t<span style={styles.meta}>{isPreviewing ? 'Rendering…' : 'Live'}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t{previewError && (\n\t\t\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t\t\t{previewError.explanation}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)}\n\t\t\t\t\t{preview?.subject && <p style={styles.previewSubject}>{preview.subject}</p>}\n\t\t\t\t\t{preview?.html ? (\n\t\t\t\t\t\t// sandbox=\"\" — no scripts, no same-origin. This is operator-authored\n\t\t\t\t\t\t// HTML and it must never execute in the dashboard's origin, which is\n\t\t\t\t\t\t// where the admin token lives.\n\t\t\t\t\t\t<iframe\n\t\t\t\t\t\t\ttitle=\"Template preview\"\n\t\t\t\t\t\t\tstyle={styles.previewFrame}\n\t\t\t\t\t\t\tsandbox=\"\"\n\t\t\t\t\t\t\tsrcDoc={preview.html}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : preview ? (\n\t\t\t\t\t\t<pre style={styles.previewText}>{preview.text}</pre>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<p style={styles.meta}>Nothing rendered yet.</p>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{revisions.length > 0 && (\n\t\t\t\t<div style={styles.revisions}>\n\t\t\t\t\t<h2 style={styles.subtitle}>History</h2>\n\t\t\t\t\t<ul style={styles.list}>\n\t\t\t\t\t\t{revisions.map((rev) => (\n\t\t\t\t\t\t\t<li key={rev.version} style={styles.row}>\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\tv{rev.version} — {rev.actor ?? 'unknown'} —{' '}\n\t\t\t\t\t\t\t\t\t{new Date(rev.createdAt).toLocaleString()}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => rollback(rev.version)}\n\t\t\t\t\t\t\t\t\tstyle={styles.rollbackButton}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tRoll back\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '8px 40px 64px', maxWidth: 1400, boxSizing: 'border-box' },\n\t// Two columns, always: the editor on the left, the rendered result on the\n\t// right. Stacking the preview under a long form hid it below the fold.\n\tsplit: {\n\t\tdisplay: 'grid',\n\t\tgridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)',\n\t\tgap: 24,\n\t\talignItems: 'start',\n\t},\n\tcolumn: { minWidth: 0, display: 'flex', flexDirection: 'column' },\n\t// Pinned while the form scrolls, so the preview stays in view as you edit.\n\tpreviewColumn: {\n\t\tminWidth: 0,\n\t\tdisplay: 'flex',\n\t\tflexDirection: 'column',\n\t\tposition: 'sticky',\n\t\ttop: 16,\n\t},\n\tsaveRow: { display: 'flex', alignItems: 'center', gap: 12, marginTop: 16 },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: 12,\n\t},\n\tpreviewSubject: { fontSize: 14, fontWeight: 600, margin: '8px 0' },\n\tpreviewFrame: {\n\t\tboxShadow: 'var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))',\n\t\twidth: '100%',\n\t\theight: 520,\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t},\n\tpreviewText: {\n\t\twhiteSpace: 'pre-wrap',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 8,\n\t\tpadding: 12,\n\t\tfontSize: 13,\n\t\tfontFamily:\n\t\t\t'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: {\n\t\tfontSize: 22,\n\t\tfontWeight: 600,\n\t\tmargin: '8px 0 4px',\n\t\tletterSpacing: 'var(--fonderie-tracking-display,-0.05em)',\n\t\tlineHeight: 1.25,\n\t},\n\tmeta: { fontSize: 13, color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: 20 },\n\tstatus: { padding: 24, textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: {\n\t\tcolor: 'var(--fonderie-danger,#e00)',\n\t\tbackground: 'color-mix(in srgb, var(--fonderie-danger,#e00) 8%, transparent)',\n\t\tborderRadius: 6,\n\t\tpadding: '8px 12px',\n\t\tmargin: '8px 0',\n\t\tfontSize: 13.5,\n\t},\n\tform: {\n\t\tdisplay: 'flex',\n\t\tflexDirection: 'column',\n\t\tgap: 4,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 'var(--fonderie-radius-lg,8px)',\n\t\tpadding: '6px 20px 20px',\n\t\tboxShadow: 'var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))',\n\t},\n\tlabel: { fontSize: 13, fontWeight: 500, marginTop: 14, marginBottom: 2 },\n\tinput: {\n\t\theight: 36,\n\t\tboxSizing: 'border-box',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 6,\n\t\tpadding: '0 10px',\n\t\tfontSize: 13.5,\n\t\tfontFamily: 'inherit',\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t},\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 6,\n\t\tpadding: 10,\n\t\tfontSize: 13,\n\t\tlineHeight: 1.55,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t\tfontFamily:\n\t\t\t'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, fontSize: 14 },\n\tbutton: {\n\t\tdisplay: 'inline-flex',\n\t\talignItems: 'center',\n\t\theight: 36,\n\t\tboxSizing: 'border-box',\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '0 16px',\n\t\tborderRadius: 6,\n\t\tborder: '1px solid var(--fonderie-text,#171717)',\n\t\tfontSize: 13.5,\n\t\tfontWeight: 600,\n\t\tfontFamily: 'inherit',\n\t\tcursor: 'pointer',\n\t\talignSelf: 'flex-start',\n\t},\n\tbuttonDisabled: {\n\t\tdisplay: 'inline-flex',\n\t\talignItems: 'center',\n\t\theight: 36,\n\t\tboxSizing: 'border-box',\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '0 16px',\n\t\tborderRadius: 6,\n\t\tborder: '1px solid var(--fonderie-text,#171717)',\n\t\tfontSize: 13.5,\n\t\tfontWeight: 600,\n\t\tfontFamily: 'inherit',\n\t\talignSelf: 'flex-start',\n\t\topacity: 0.45,\n\t\tcursor: 'not-allowed',\n\t},\n\trevisions: { marginTop: 32 },\n\tsubtitle: {\n\t\tfontSize: 15,\n\t\tfontWeight: 600,\n\t\tmargin: '0 0 10px',\n\t\tletterSpacing: 'var(--fonderie-tracking-display,-0.05em)',\n\t},\n\tlist: {\n\t\tlistStyle: 'none',\n\t\tpadding: 0,\n\t\tmargin: 0,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 'var(--fonderie-radius-lg,8px)',\n\t\toverflow: 'hidden',\n\t},\n\trow: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tgap: 12,\n\t\tpadding: '9px 16px',\n\t\tborderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)',\n\t\tfontSize: 13,\n\t},\n\trollbackButton: {\n\t\tdisplay: 'inline-flex',\n\t\talignItems: 'center',\n\t\theight: 28,\n\t\tboxSizing: 'border-box',\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 6,\n\t\tpadding: '0 10px',\n\t\tfontSize: 12.5,\n\t\tfontWeight: 500,\n\t\tfontFamily: 'inherit',\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/react-courier-admin';\nimport type { CSSProperties } from 'react';\n\nexport interface ITemplateListScreenProps {\n\tclient: CourierAdminClient;\n\tonSelectTemplate?: (template: ITemplateEntry) => void;\n}\n\nexport function TemplateListScreen({ client, onSelectTemplate }: ITemplateListScreenProps) {\n\tconst { templates, isLoading, error } = useTemplates(client);\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h1 style={styles.title}>Templates</h1>\n\t\t\t<p style={styles.hint}>\n\t\t\t\tEvery email the app sends. Open one to edit its copy and preview it live.\n\t\t\t</p>\n\t\t\t{isLoading ? (\n\t\t\t\t<p style={styles.status}>Loading templates…</p>\n\t\t\t) : error ? (\n\t\t\t\t<p style={styles.error} role=\"alert\">\n\t\t\t\t\t{error.explanation}\n\t\t\t\t</p>\n\t\t\t) : (\n\t\t\t\t<ul style={styles.list}>\n\t\t\t\t\t{templates.map((template) => (\n\t\t\t\t\t\t<li key={`${template.type}:${template.locale ?? 'base'}`} style={styles.row}>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => onSelectTemplate?.(template)}\n\t\t\t\t\t\t\t\tstyle={styles.rowButton}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span style={styles.type}>{template.type}</span>\n\t\t\t\t\t\t\t\t<span style={styles.locale}>{template.locale ?? 'default locale'}</span>\n\t\t\t\t\t\t\t\t<span style={template.active ? styles.active : styles.inactive}>\n\t\t\t\t\t\t\t\t\t<span style={styles.dot} />\n\t\t\t\t\t\t\t\t\t{template.active ? 'active' : 'inactive'}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n// Matches @fonderie/react-admin-screens' shell look with local values: this\n// package sits below the admin screens and cannot import their blocks.\nconst pill: CSSProperties = {\n\tdisplay: 'inline-flex',\n\talignItems: 'center',\n\tgap: 6,\n\tborderRadius: 999,\n\tpadding: '2px 9px 2px 8px',\n\tfontSize: 12,\n\tfontWeight: 600,\n\tlineHeight: 1.5,\n\tminWidth: 64,\n};\n\nconst styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '32px 40px 64px', maxWidth: 1160, boxSizing: 'border-box' },\n\ttitle: {\n\t\tfontSize: 22,\n\t\tfontWeight: 600,\n\t\tmargin: 0,\n\t\tletterSpacing: 'var(--fonderie-tracking-display,-0.05em)',\n\t\tlineHeight: 1.25,\n\t},\n\thint: { fontSize: 13.5, color: 'var(--fonderie-text-muted,#5c5c5c)', margin: '4px 0 24px' },\n\tstatus: { padding: '24px 0', color: 'var(--fonderie-text-muted,#5c5c5c)', fontSize: 13.5 },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: 12, fontSize: 14 },\n\tlist: {\n\t\tlistStyle: 'none',\n\t\tpadding: 0,\n\t\tmargin: 0,\n\t\tbackground: 'var(--fonderie-surface,#fff)',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: 'var(--fonderie-radius-lg,8px)',\n\t\toverflow: 'hidden',\n\t\tboxShadow: 'var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))',\n\t},\n\trow: { borderBottom: '1px solid var(--fonderie-border-light,#f5f5f5)' },\n\trowButton: {\n\t\twidth: '100%',\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tgap: 16,\n\t\tpadding: '13px 16px',\n\t\tbackground: 'none',\n\t\tborder: 'none',\n\t\tcursor: 'pointer',\n\t\ttextAlign: 'left',\n\t\tcolor: 'var(--fonderie-text,#171717)',\n\t\tfontFamily: 'inherit',\n\t},\n\ttype: {\n\t\tfontSize: 13,\n\t\tfontWeight: 600,\n\t\tflex: 1,\n\t\tfontFamily:\n\t\t\t'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tlocale: { fontSize: 12.5, color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\tdot: { width: 6, height: 6, borderRadius: 999, background: 'currentColor' },\n\tactive: {\n\t\t...pill,\n\t\tcolor: 'var(--fonderie-accent-strong,#009767)',\n\t\tbackground: 'color-mix(in srgb, var(--fonderie-accent-strong,#009767) 13%, transparent)',\n\t},\n\tinactive: {\n\t\t...pill,\n\t\tcolor: 'var(--fonderie-text-muted,#5c5c5c)',\n\t\tbackground: 'color-mix(in srgb, var(--fonderie-text-muted,#5c5c5c) 10%, transparent)',\n\t},\n};\n"],"mappings":"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","names":["useTemplates","jsx","jsxs","styles"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonderie/react-courier-admin-screens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Pre-built React screens for the Fonderie courier admin API — template list, template editor with revision history and rollback.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fonderiejs",
|