@fonderie/react-courier-admin-screens 0.5.0 → 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 +179 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +179 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -167,7 +167,15 @@ function TemplateEditorScreen({
|
|
|
167
167
|
] }),
|
|
168
168
|
saveError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.error, role: "alert", children: saveError.explanation }),
|
|
169
169
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.saveRow, children: [
|
|
170
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
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
|
+
),
|
|
171
179
|
!dirty && !isSaving && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.meta, children: "No changes to save" })
|
|
172
180
|
] })
|
|
173
181
|
] }),
|
|
@@ -234,13 +242,24 @@ function TemplateEditorScreen({
|
|
|
234
242
|
] });
|
|
235
243
|
}
|
|
236
244
|
var styles = {
|
|
237
|
-
container: { padding:
|
|
245
|
+
container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
|
|
238
246
|
// Two columns, always: the editor on the left, the rendered result on the
|
|
239
247
|
// right. Stacking the preview under a long form hid it below the fold.
|
|
240
|
-
split: {
|
|
248
|
+
split: {
|
|
249
|
+
display: "grid",
|
|
250
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
|
|
251
|
+
gap: 24,
|
|
252
|
+
alignItems: "start"
|
|
253
|
+
},
|
|
241
254
|
column: { minWidth: 0, display: "flex", flexDirection: "column" },
|
|
242
255
|
// Pinned while the form scrolls, so the preview stays in view as you edit.
|
|
243
|
-
previewColumn: {
|
|
256
|
+
previewColumn: {
|
|
257
|
+
minWidth: 0,
|
|
258
|
+
display: "flex",
|
|
259
|
+
flexDirection: "column",
|
|
260
|
+
position: "sticky",
|
|
261
|
+
top: 16
|
|
262
|
+
},
|
|
244
263
|
saveRow: { display: "flex", alignItems: "center", gap: 12, marginTop: 16 },
|
|
245
264
|
previewHeader: {
|
|
246
265
|
display: "flex",
|
|
@@ -250,6 +269,7 @@ var styles = {
|
|
|
250
269
|
},
|
|
251
270
|
previewSubject: { fontSize: 14, fontWeight: 600, margin: "8px 0" },
|
|
252
271
|
previewFrame: {
|
|
272
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))",
|
|
253
273
|
width: "100%",
|
|
254
274
|
height: 520,
|
|
255
275
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
@@ -264,60 +284,127 @@ var styles = {
|
|
|
264
284
|
fontSize: 13,
|
|
265
285
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
266
286
|
},
|
|
267
|
-
title: {
|
|
268
|
-
|
|
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 },
|
|
269
295
|
status: { padding: 24, textAlign: "center", color: "var(--fonderie-text-muted,#5c5c5c)" },
|
|
270
|
-
error: {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
+
},
|
|
274
326
|
textarea: {
|
|
275
327
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
276
|
-
borderRadius:
|
|
328
|
+
borderRadius: 6,
|
|
277
329
|
padding: 10,
|
|
278
|
-
fontSize:
|
|
330
|
+
fontSize: 13,
|
|
331
|
+
lineHeight: 1.55,
|
|
332
|
+
background: "var(--fonderie-surface,#fff)",
|
|
333
|
+
color: "var(--fonderie-text,#171717)",
|
|
279
334
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
280
335
|
},
|
|
281
336
|
checkboxLabel: { display: "flex", alignItems: "center", gap: 8, marginTop: 12, fontSize: 14 },
|
|
282
337
|
button: {
|
|
338
|
+
display: "inline-flex",
|
|
339
|
+
alignItems: "center",
|
|
340
|
+
height: 36,
|
|
341
|
+
boxSizing: "border-box",
|
|
283
342
|
backgroundColor: "var(--fonderie-text,#171717)",
|
|
284
343
|
color: "var(--fonderie-surface,#fff)",
|
|
285
|
-
padding: "
|
|
286
|
-
borderRadius:
|
|
287
|
-
border: "
|
|
288
|
-
fontSize:
|
|
344
|
+
padding: "0 16px",
|
|
345
|
+
borderRadius: 6,
|
|
346
|
+
border: "1px solid var(--fonderie-text,#171717)",
|
|
347
|
+
fontSize: 13.5,
|
|
289
348
|
fontWeight: 600,
|
|
349
|
+
fontFamily: "inherit",
|
|
290
350
|
cursor: "pointer",
|
|
291
351
|
alignSelf: "flex-start"
|
|
292
352
|
},
|
|
293
353
|
buttonDisabled: {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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,
|
|
300
364
|
fontWeight: 600,
|
|
301
|
-
|
|
302
|
-
alignSelf: "flex-start"
|
|
365
|
+
fontFamily: "inherit",
|
|
366
|
+
alignSelf: "flex-start",
|
|
367
|
+
opacity: 0.45,
|
|
368
|
+
cursor: "not-allowed"
|
|
303
369
|
},
|
|
304
370
|
revisions: { marginTop: 32 },
|
|
305
|
-
subtitle: {
|
|
306
|
-
|
|
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
|
+
},
|
|
307
386
|
row: {
|
|
308
387
|
display: "flex",
|
|
309
388
|
justifyContent: "space-between",
|
|
310
389
|
alignItems: "center",
|
|
311
|
-
|
|
390
|
+
gap: 12,
|
|
391
|
+
padding: "9px 16px",
|
|
312
392
|
borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)",
|
|
313
393
|
fontSize: 13
|
|
314
394
|
},
|
|
315
395
|
rollbackButton: {
|
|
316
|
-
|
|
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)",
|
|
317
402
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
318
|
-
borderRadius:
|
|
319
|
-
padding: "
|
|
320
|
-
fontSize: 12,
|
|
403
|
+
borderRadius: 6,
|
|
404
|
+
padding: "0 10px",
|
|
405
|
+
fontSize: 12.5,
|
|
406
|
+
fontWeight: 500,
|
|
407
|
+
fontFamily: "inherit",
|
|
321
408
|
cursor: "pointer"
|
|
322
409
|
}
|
|
323
410
|
};
|
|
@@ -327,12 +414,10 @@ var import_react_courier_admin2 = require("@fonderie/react-courier-admin");
|
|
|
327
414
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
328
415
|
function TemplateListScreen({ client, onSelectTemplate }) {
|
|
329
416
|
const { templates, isLoading, error } = (0, import_react_courier_admin2.useTemplates)(client);
|
|
330
|
-
if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles2.status, children: "Loading templates\u2026" });
|
|
331
|
-
if (error)
|
|
332
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles2.error, role: "alert", children: error.explanation });
|
|
333
417
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles2.container, children: [
|
|
334
418
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { style: styles2.title, children: "Templates" }),
|
|
335
|
-
/* @__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)(
|
|
336
421
|
"button",
|
|
337
422
|
{
|
|
338
423
|
type: "button",
|
|
@@ -340,35 +425,82 @@ function TemplateListScreen({ client, onSelectTemplate }) {
|
|
|
340
425
|
style: styles2.rowButton,
|
|
341
426
|
children: [
|
|
342
427
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles2.type, children: template.type }),
|
|
343
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles2.locale, children: template.locale ?? "
|
|
344
|
-
/* @__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
|
+
] })
|
|
345
433
|
]
|
|
346
434
|
}
|
|
347
435
|
) }, `${template.type}:${template.locale ?? "base"}`)) })
|
|
348
436
|
] });
|
|
349
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
|
+
};
|
|
350
449
|
var styles2 = {
|
|
351
|
-
container: { padding:
|
|
352
|
-
title: {
|
|
353
|
-
|
|
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 },
|
|
354
460
|
error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
|
|
355
|
-
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
|
+
},
|
|
356
471
|
row: { borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)" },
|
|
357
472
|
rowButton: {
|
|
358
473
|
width: "100%",
|
|
359
474
|
display: "flex",
|
|
360
475
|
justifyContent: "space-between",
|
|
361
476
|
alignItems: "center",
|
|
362
|
-
|
|
477
|
+
gap: 16,
|
|
478
|
+
padding: "13px 16px",
|
|
363
479
|
background: "none",
|
|
364
480
|
border: "none",
|
|
365
481
|
cursor: "pointer",
|
|
366
|
-
textAlign: "left"
|
|
482
|
+
textAlign: "left",
|
|
483
|
+
color: "var(--fonderie-text,#171717)",
|
|
484
|
+
fontFamily: "inherit"
|
|
485
|
+
},
|
|
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)'
|
|
367
491
|
},
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
active: {
|
|
371
|
-
|
|
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
|
+
}
|
|
372
504
|
};
|
|
373
505
|
// Annotate the CommonJS export names for ESM import in node:
|
|
374
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, 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<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<div style={styles.saveRow}>\n\t\t\t\t\t<button type=\"submit\" disabled={isSaving || !dirty} style={dirty ? styles.button : styles.buttonDisabled}>\n\t\t\t\t\t\t{isSaving ? 'Saving…' : 'Save'}\n\t\t\t\t\t</button>\n\t\t\t\t\t{!dirty && !isSaving && <span style={styles.meta}>No changes to save</span>}\n\t\t\t\t</div>\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.previewColumn}>\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<span style={styles.meta}>{isPreviewing ? 'Rendering…' : 'Live'}</span>\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 },\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: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 24, alignItems: 'start' },\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: { minWidth: 0, display: 'flex', flexDirection: 'column', position: 'sticky', top: 16 },\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\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\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\tbuttonDisabled: {\n\t\tbackgroundColor: 'var(--fonderie-border,#e0e0e0)',\n\t\tcolor: 'var(--fonderie-text-muted,#5c5c5c)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: 8,\n\t\tborder: 'none',\n\t\tfontSize: 14,\n\t\tfontWeight: 600,\n\t\tcursor: 'not-allowed',\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
|
@@ -145,7 +145,15 @@ function TemplateEditorScreen({
|
|
|
145
145
|
] }),
|
|
146
146
|
saveError && /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: saveError.explanation }),
|
|
147
147
|
/* @__PURE__ */ jsxs("div", { style: styles.saveRow, children: [
|
|
148
|
-
/* @__PURE__ */ jsx(
|
|
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
|
+
),
|
|
149
157
|
!dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: "No changes to save" })
|
|
150
158
|
] })
|
|
151
159
|
] }),
|
|
@@ -212,13 +220,24 @@ function TemplateEditorScreen({
|
|
|
212
220
|
] });
|
|
213
221
|
}
|
|
214
222
|
var styles = {
|
|
215
|
-
container: { padding:
|
|
223
|
+
container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
|
|
216
224
|
// Two columns, always: the editor on the left, the rendered result on the
|
|
217
225
|
// right. Stacking the preview under a long form hid it below the fold.
|
|
218
|
-
split: {
|
|
226
|
+
split: {
|
|
227
|
+
display: "grid",
|
|
228
|
+
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
|
|
229
|
+
gap: 24,
|
|
230
|
+
alignItems: "start"
|
|
231
|
+
},
|
|
219
232
|
column: { minWidth: 0, display: "flex", flexDirection: "column" },
|
|
220
233
|
// Pinned while the form scrolls, so the preview stays in view as you edit.
|
|
221
|
-
previewColumn: {
|
|
234
|
+
previewColumn: {
|
|
235
|
+
minWidth: 0,
|
|
236
|
+
display: "flex",
|
|
237
|
+
flexDirection: "column",
|
|
238
|
+
position: "sticky",
|
|
239
|
+
top: 16
|
|
240
|
+
},
|
|
222
241
|
saveRow: { display: "flex", alignItems: "center", gap: 12, marginTop: 16 },
|
|
223
242
|
previewHeader: {
|
|
224
243
|
display: "flex",
|
|
@@ -228,6 +247,7 @@ var styles = {
|
|
|
228
247
|
},
|
|
229
248
|
previewSubject: { fontSize: 14, fontWeight: 600, margin: "8px 0" },
|
|
230
249
|
previewFrame: {
|
|
250
|
+
boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))",
|
|
231
251
|
width: "100%",
|
|
232
252
|
height: 520,
|
|
233
253
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
@@ -242,60 +262,127 @@ var styles = {
|
|
|
242
262
|
fontSize: 13,
|
|
243
263
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
244
264
|
},
|
|
245
|
-
title: {
|
|
246
|
-
|
|
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 },
|
|
247
273
|
status: { padding: 24, textAlign: "center", color: "var(--fonderie-text-muted,#5c5c5c)" },
|
|
248
|
-
error: {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
},
|
|
252
304
|
textarea: {
|
|
253
305
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
254
|
-
borderRadius:
|
|
306
|
+
borderRadius: 6,
|
|
255
307
|
padding: 10,
|
|
256
|
-
fontSize:
|
|
308
|
+
fontSize: 13,
|
|
309
|
+
lineHeight: 1.55,
|
|
310
|
+
background: "var(--fonderie-surface,#fff)",
|
|
311
|
+
color: "var(--fonderie-text,#171717)",
|
|
257
312
|
fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
|
|
258
313
|
},
|
|
259
314
|
checkboxLabel: { display: "flex", alignItems: "center", gap: 8, marginTop: 12, fontSize: 14 },
|
|
260
315
|
button: {
|
|
316
|
+
display: "inline-flex",
|
|
317
|
+
alignItems: "center",
|
|
318
|
+
height: 36,
|
|
319
|
+
boxSizing: "border-box",
|
|
261
320
|
backgroundColor: "var(--fonderie-text,#171717)",
|
|
262
321
|
color: "var(--fonderie-surface,#fff)",
|
|
263
|
-
padding: "
|
|
264
|
-
borderRadius:
|
|
265
|
-
border: "
|
|
266
|
-
fontSize:
|
|
322
|
+
padding: "0 16px",
|
|
323
|
+
borderRadius: 6,
|
|
324
|
+
border: "1px solid var(--fonderie-text,#171717)",
|
|
325
|
+
fontSize: 13.5,
|
|
267
326
|
fontWeight: 600,
|
|
327
|
+
fontFamily: "inherit",
|
|
268
328
|
cursor: "pointer",
|
|
269
329
|
alignSelf: "flex-start"
|
|
270
330
|
},
|
|
271
331
|
buttonDisabled: {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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,
|
|
278
342
|
fontWeight: 600,
|
|
279
|
-
|
|
280
|
-
alignSelf: "flex-start"
|
|
343
|
+
fontFamily: "inherit",
|
|
344
|
+
alignSelf: "flex-start",
|
|
345
|
+
opacity: 0.45,
|
|
346
|
+
cursor: "not-allowed"
|
|
281
347
|
},
|
|
282
348
|
revisions: { marginTop: 32 },
|
|
283
|
-
subtitle: {
|
|
284
|
-
|
|
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
|
+
},
|
|
285
364
|
row: {
|
|
286
365
|
display: "flex",
|
|
287
366
|
justifyContent: "space-between",
|
|
288
367
|
alignItems: "center",
|
|
289
|
-
|
|
368
|
+
gap: 12,
|
|
369
|
+
padding: "9px 16px",
|
|
290
370
|
borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)",
|
|
291
371
|
fontSize: 13
|
|
292
372
|
},
|
|
293
373
|
rollbackButton: {
|
|
294
|
-
|
|
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)",
|
|
295
380
|
border: "1px solid var(--fonderie-border,#e0e0e0)",
|
|
296
|
-
borderRadius:
|
|
297
|
-
padding: "
|
|
298
|
-
fontSize: 12,
|
|
381
|
+
borderRadius: 6,
|
|
382
|
+
padding: "0 10px",
|
|
383
|
+
fontSize: 12.5,
|
|
384
|
+
fontWeight: 500,
|
|
385
|
+
fontFamily: "inherit",
|
|
299
386
|
cursor: "pointer"
|
|
300
387
|
}
|
|
301
388
|
};
|
|
@@ -305,12 +392,10 @@ import { useTemplates as useTemplates2 } from "@fonderie/react-courier-admin";
|
|
|
305
392
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
306
393
|
function TemplateListScreen({ client, onSelectTemplate }) {
|
|
307
394
|
const { templates, isLoading, error } = useTemplates2(client);
|
|
308
|
-
if (isLoading) return /* @__PURE__ */ jsx2("p", { style: styles2.status, children: "Loading templates\u2026" });
|
|
309
|
-
if (error)
|
|
310
|
-
return /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation });
|
|
311
395
|
return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
|
|
312
396
|
/* @__PURE__ */ jsx2("h1", { style: styles2.title, children: "Templates" }),
|
|
313
|
-
/* @__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(
|
|
314
399
|
"button",
|
|
315
400
|
{
|
|
316
401
|
type: "button",
|
|
@@ -318,35 +403,82 @@ function TemplateListScreen({ client, onSelectTemplate }) {
|
|
|
318
403
|
style: styles2.rowButton,
|
|
319
404
|
children: [
|
|
320
405
|
/* @__PURE__ */ jsx2("span", { style: styles2.type, children: template.type }),
|
|
321
|
-
/* @__PURE__ */ jsx2("span", { style: styles2.locale, children: template.locale ?? "
|
|
322
|
-
/* @__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
|
+
] })
|
|
323
411
|
]
|
|
324
412
|
}
|
|
325
413
|
) }, `${template.type}:${template.locale ?? "base"}`)) })
|
|
326
414
|
] });
|
|
327
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
|
+
};
|
|
328
427
|
var styles2 = {
|
|
329
|
-
container: { padding:
|
|
330
|
-
title: {
|
|
331
|
-
|
|
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 },
|
|
332
438
|
error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
|
|
333
|
-
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
|
+
},
|
|
334
449
|
row: { borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)" },
|
|
335
450
|
rowButton: {
|
|
336
451
|
width: "100%",
|
|
337
452
|
display: "flex",
|
|
338
453
|
justifyContent: "space-between",
|
|
339
454
|
alignItems: "center",
|
|
340
|
-
|
|
455
|
+
gap: 16,
|
|
456
|
+
padding: "13px 16px",
|
|
341
457
|
background: "none",
|
|
342
458
|
border: "none",
|
|
343
459
|
cursor: "pointer",
|
|
344
|
-
textAlign: "left"
|
|
460
|
+
textAlign: "left",
|
|
461
|
+
color: "var(--fonderie-text,#171717)",
|
|
462
|
+
fontFamily: "inherit"
|
|
463
|
+
},
|
|
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)'
|
|
345
469
|
},
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
active: {
|
|
349
|
-
|
|
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
|
+
}
|
|
350
482
|
};
|
|
351
483
|
export {
|
|
352
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, 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<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<div style={styles.saveRow}>\n\t\t\t\t\t<button type=\"submit\" disabled={isSaving || !dirty} style={dirty ? styles.button : styles.buttonDisabled}>\n\t\t\t\t\t\t{isSaving ? 'Saving…' : 'Save'}\n\t\t\t\t\t</button>\n\t\t\t\t\t{!dirty && !isSaving && <span style={styles.meta}>No changes to save</span>}\n\t\t\t\t</div>\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.previewColumn}>\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<span style={styles.meta}>{isPreviewing ? 'Rendering…' : 'Live'}</span>\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 },\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: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: 24, alignItems: 'start' },\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: { minWidth: 0, display: 'flex', flexDirection: 'column', position: 'sticky', top: 16 },\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\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\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\tbuttonDisabled: {\n\t\tbackgroundColor: 'var(--fonderie-border,#e0e0e0)',\n\t\tcolor: 'var(--fonderie-text-muted,#5c5c5c)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: 8,\n\t\tborder: 'none',\n\t\tfontSize: 14,\n\t\tfontWeight: 600,\n\t\tcursor: 'not-allowed',\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",
|