@fonderie/vue-courier-admin-screens 0.4.1 → 0.5.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 CHANGED
@@ -31,9 +31,14 @@ var import_vue = require("vue");
31
31
 
32
32
  // src/styles.ts
33
33
  var styles = {
34
- container: { padding: "24px", maxWidth: "1180px" },
35
- split: { display: "flex", gap: "24px", alignItems: "flex-start", flexWrap: "wrap" },
36
- column: { flex: "1 1 460px", minWidth: "320px", display: "flex", flexDirection: "column" },
34
+ container: { padding: "24px" },
35
+ // Two columns, always: editor left, rendered result right. Stacking the
36
+ // preview under a long form hid it below the fold.
37
+ split: { display: "grid", gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)", gap: "24px", alignItems: "start" },
38
+ column: { minWidth: "0", display: "flex", flexDirection: "column" },
39
+ // Pinned while the form scrolls, so the preview stays in view as you edit.
40
+ previewColumn: { minWidth: "0", display: "flex", flexDirection: "column", position: "sticky", top: "16px" },
41
+ saveRow: { display: "flex", alignItems: "center", gap: "12px", marginTop: "16px" },
37
42
  previewHeader: {
38
43
  display: "flex",
39
44
  justifyContent: "space-between",
@@ -96,7 +101,6 @@ var styles = {
96
101
  fontSize: "14px"
97
102
  },
98
103
  button: {
99
- marginTop: "16px",
100
104
  backgroundColor: "var(--fonderie-text,#171717)",
101
105
  color: "var(--fonderie-surface,#fff)",
102
106
  padding: "10px 20px",
@@ -107,6 +111,17 @@ var styles = {
107
111
  cursor: "pointer",
108
112
  alignSelf: "flex-start"
109
113
  },
114
+ buttonDisabled: {
115
+ backgroundColor: "var(--fonderie-border,#e0e0e0)",
116
+ color: "var(--fonderie-text-muted,#5c5c5c)",
117
+ padding: "10px 20px",
118
+ borderRadius: "8px",
119
+ border: "none",
120
+ fontSize: "14px",
121
+ fontWeight: 600,
122
+ cursor: "not-allowed",
123
+ alignSelf: "flex-start"
124
+ },
110
125
  revisions: { marginTop: "32px" },
111
126
  revisionRow: {
112
127
  display: "flex",
@@ -127,6 +142,7 @@ var styles = {
127
142
  };
128
143
 
129
144
  // src/screens/TemplateEditorScreen.ts
145
+ var PREVIEW_DEBOUNCE_MS = 500;
130
146
  var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
131
147
  var TemplateEditorScreen = (0, import_vue.defineComponent)({
132
148
  name: "FonderieTemplateEditorScreen",
@@ -200,8 +216,15 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
200
216
  );
201
217
  }
202
218
  }
203
- (0, import_vue.watch)(template, (tpl) => {
204
- if (tpl) void run();
219
+ let previewTimer;
220
+ (0, import_vue.watch)([template, subject, html, text, sampleJson], () => {
221
+ if (!template.value) return;
222
+ clearTimeout(previewTimer);
223
+ previewTimer = setTimeout(() => void run(), PREVIEW_DEBOUNCE_MS);
224
+ });
225
+ const dirty = (0, import_vue.computed)(() => {
226
+ const tpl = template.value;
227
+ return !!tpl && (subject.value !== (tpl.subject ?? "") || html.value !== (tpl.html ?? "") || text.value !== tpl.text || active.value !== tpl.active);
205
228
  });
206
229
  async function handleSubmit(event) {
207
230
  event.preventDefault();
@@ -289,11 +312,18 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
289
312
  "Active"
290
313
  ]),
291
314
  saveError.value ? (0, import_vue.h)("p", { style: styles.error, role: "alert" }, saveError.value.explanation) : null,
292
- (0, import_vue.h)(
293
- "button",
294
- { type: "submit", disabled: isSaving.value, style: styles.button },
295
- isSaving.value ? "Saving\u2026" : "Save"
296
- )
315
+ (0, import_vue.h)("div", { style: styles.saveRow }, [
316
+ (0, import_vue.h)(
317
+ "button",
318
+ {
319
+ type: "submit",
320
+ disabled: isSaving.value || !dirty.value,
321
+ style: dirty.value ? styles.button : styles.buttonDisabled
322
+ },
323
+ isSaving.value ? "Saving\u2026" : "Save"
324
+ ),
325
+ !dirty.value && !isSaving.value ? (0, import_vue.h)("span", { style: styles.meta }, "No changes to save") : null
326
+ ])
297
327
  ]),
298
328
  (0, import_vue.h)("label", { style: styles.label, for: "template-sample" }, "Sample data"),
299
329
  (0, import_vue.h)("textarea", {
@@ -308,19 +338,10 @@ var TemplateEditorScreen = (0, import_vue.defineComponent)({
308
338
  }),
309
339
  sampleError.value ? (0, import_vue.h)("p", { style: styles.error, role: "alert" }, sampleError.value) : null
310
340
  ]),
311
- (0, import_vue.h)("div", { style: styles.column }, [
341
+ (0, import_vue.h)("div", { style: styles.previewColumn }, [
312
342
  (0, import_vue.h)("div", { style: styles.previewHeader }, [
313
343
  (0, import_vue.h)("span", { style: styles.label }, "Preview"),
314
- (0, import_vue.h)(
315
- "button",
316
- {
317
- type: "button",
318
- style: styles.rollbackButton,
319
- disabled: isPreviewing.value,
320
- onClick: () => void run()
321
- },
322
- isPreviewing.value ? "Rendering\u2026" : "Render"
323
- )
344
+ (0, import_vue.h)("span", { style: styles.meta }, isPreviewing.value ? "Rendering\u2026" : "Live")
324
345
  ]),
325
346
  previewError.value ? (0, import_vue.h)("p", { style: styles.error, role: "alert" }, previewError.value.explanation) : null,
326
347
  preview.value?.subject ? (0, import_vue.h)("p", { style: styles.previewSubject }, preview.value.subject) : null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/screens/TemplateEditorScreen.ts","../src/styles.ts","../src/screens/TemplateListScreen.ts"],"sourcesContent":["export { TemplateEditorScreen, TemplateListScreen } from './screens';\n","import type {\n\tCourierAdminClient,\n\tFonderieApiError,\n\tISetTemplateInput,\n\tITemplateRevision,\n} from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { defineComponent, h, ref, watch } from 'vue';\nimport { styles } from '../styles';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nexport const TemplateEditorScreen = defineComponent({\n\tname: 'FonderieTemplateEditorScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t\ttype: { type: String, required: true },\n\t\tlocale: { type: String as PropType<string | null>, default: null },\n\t},\n\temits: {\n\t\tsaved: () => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { template, isLoading, error, refresh } = useTemplate(\n\t\t\tprops.client,\n\t\t\tprops.type,\n\t\t\tprops.locale,\n\t\t);\n\t\t// Saves go through the list composable (which re-fetches the template list\n\t\t// after each write); mounting it adds a template-list fetch to this\n\t\t// single-template editor — acceptable for an admin dashboard. Its\n\t\t// isLoading/error track that list fetch, so the save action keeps local state.\n\t\tconst { saveTemplate } = useTemplates(props.client);\n\t\tconst { revisions, rollback } = useTemplateRevisions(props.client, props.type, props.locale);\n\t\tconst isSaving = ref(false);\n\t\tconst saveError = ref<FonderieApiError | null>(null);\n\n\t\tconst subject = ref('');\n\t\tconst html = ref('');\n\t\tconst text = ref('');\n\t\tconst active = ref(true);\n\n\t\twatch(template, (tpl) => {\n\t\t\tif (!tpl) return;\n\t\t\tsubject.value = tpl.subject ?? '';\n\t\t\thtml.value = tpl.html ?? '';\n\t\t\ttext.value = tpl.text;\n\t\t\tactive.value = tpl.active;\n\t\t});\n\n\t\tconst {\n\t\t\tpreview,\n\t\t\tisPreviewing,\n\t\t\terror: previewError,\n\t\t\trenderPreview,\n\t\t} = useTemplatePreview(props.client);\n\t\t// Sample values as JSON text rather than an object, so a half-typed value\n\t\t// does not have to parse on every keystroke.\n\t\tconst sampleJson = ref('{}');\n\t\tconst sampleError = ref<string | null>(null);\n\n\t\tasync function run() {\n\t\t\tlet data: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(sampleJson.value || '{}');\n\t\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\t\tsampleError.value = 'Sample data must be a JSON object.';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\t\tsampleError.value = null;\n\t\t\t} catch {\n\t\t\t\tsampleError.value = 'Sample data is not valid JSON.';\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = await renderPreview(\n\t\t\t\tprops.type,\n\t\t\t\t{\n\t\t\t\t\ttext: text.value,\n\t\t\t\t\tdata,\n\t\t\t\t\t...(subject.value ? { subject: subject.value } : {}),\n\t\t\t\t\t...(html.value ? { html: html.value } : {}),\n\t\t\t\t},\n\t\t\t\tprops.locale,\n\t\t\t);\n\t\t\t// The server reports which variables this content uses; seed the ones\n\t\t\t// with no value yet with their own name so the render shows where each\n\t\t\t// lands. Never overwrite something already typed.\n\t\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\t\tif (missing.length > 0) {\n\t\t\t\tsampleJson.value = JSON.stringify(\n\t\t\t\t\t{ ...data, ...Object.fromEntries(missing.map((v) => [v, v])) },\n\t\t\t\t\tnull,\n\t\t\t\t\t2,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// One render once the template has loaded, so the pane is never empty on\n\t\t// arrival. After that it is explicit — a request per keystroke is not a\n\t\t// preview, it is a load test.\n\t\twatch(template, (tpl) => {\n\t\t\tif (tpl) void run();\n\t\t});\n\n\t\tasync function handleSubmit(event: Event) {\n\t\t\tevent.preventDefault();\n\t\t\tisSaving.value = true;\n\t\t\tsaveError.value = null;\n\t\t\ttry {\n\t\t\t\tconst input: ISetTemplateInput = { text: text.value, active: active.value };\n\t\t\t\tif (subject.value) input.subject = subject.value;\n\t\t\t\tif (html.value) input.html = html.value;\n\t\t\t\tif (template.value?.version !== undefined) input.ifVersion = template.value.version;\n\t\t\t\tawait saveTemplate(props.type, input, props.locale);\n\t\t\t\t// The list composable refreshes its own list; this screen renders the\n\t\t\t\t// single template, so re-read it too.\n\t\t\t\tawait refresh();\n\t\t\t\temit('saved');\n\t\t\t} catch (err) {\n\t\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\t\tsaveError.value = err as FonderieApiError;\n\t\t\t} finally {\n\t\t\t\tisSaving.value = false;\n\t\t\t}\n\t\t}\n\n\t\tfunction renderRevision(rev: ITemplateRevision) {\n\t\t\treturn h('li', { key: rev.version, style: styles.revisionRow }, [\n\t\t\t\th(\n\t\t\t\t\t'span',\n\t\t\t\t\t`v${rev.version} — ${rev.actor ?? 'unknown'} — ${new Date(rev.createdAt).toLocaleString()}`,\n\t\t\t\t),\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{ type: 'button', style: styles.rollbackButton, onClick: () => rollback(rev.version) },\n\t\t\t\t\t'Roll back',\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading template…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, props.type),\n\t\t\t\th(\n\t\t\t\t\t'p',\n\t\t\t\t\t{ style: styles.meta },\n\t\t\t\t\t`${props.locale ?? 'base'} · v${template.value?.version ?? 1}`,\n\t\t\t\t),\n\t\t\t\th('div', { style: styles.split }, [\n\t\t\t\th('div', { style: styles.column }, [\n\t\t\t\th('form', { style: styles.form, onSubmit: handleSubmit }, [\n\t\t\t\t\th('label', { style: styles.label, for: 'template-subject' }, 'Subject'),\n\t\t\t\t\th('input', {\n\t\t\t\t\t\tid: 'template-subject',\n\t\t\t\t\t\tstyle: styles.input,\n\t\t\t\t\t\tvalue: subject.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\tsubject.value = (e.target as HTMLInputElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-html' }, 'HTML body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-html',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 8,\n\t\t\t\t\t\tvalue: html.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\thtml.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-text' }, 'Plain-text body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-text',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 4,\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tvalue: text.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\ttext.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.checkboxLabel }, [\n\t\t\t\t\t\th('input', {\n\t\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\t\tchecked: active.value,\n\t\t\t\t\t\t\tonChange: (e: Event) => {\n\t\t\t\t\t\t\t\tactive.value = (e.target as HTMLInputElement).checked;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t'Active',\n\t\t\t\t\t]),\n\t\t\t\t\tsaveError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, saveError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\th(\n\t\t\t\t\t\t'button',\n\t\t\t\t\t\t{ type: 'submit', disabled: isSaving.value, style: styles.button },\n\t\t\t\t\t\tisSaving.value ? 'Saving…' : 'Save',\n\t\t\t\t\t),\n\t\t\t\t]),\n\t\t\t\th('label', { style: styles.label, for: 'template-sample' }, 'Sample data'),\n\t\t\t\th('textarea', {\n\t\t\t\t\tid: 'template-sample',\n\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\trows: 6,\n\t\t\t\t\tspellcheck: false,\n\t\t\t\t\tvalue: sampleJson.value,\n\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\tsampleJson.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tsampleError.value\n\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, sampleError.value)\n\t\t\t\t\t: null,\n\t\t\t\t]),\n\t\t\t\th('div', { style: styles.column }, [\n\t\t\t\t\th('div', { style: styles.previewHeader }, [\n\t\t\t\t\t\th('span', { style: styles.label }, 'Preview'),\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'button',\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t\t\tstyle: styles.rollbackButton,\n\t\t\t\t\t\t\t\tdisabled: isPreviewing.value,\n\t\t\t\t\t\t\t\tonClick: () => void run(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tisPreviewing.value ? 'Rendering…' : 'Render',\n\t\t\t\t\t\t),\n\t\t\t\t\t]),\n\t\t\t\t\tpreviewError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, previewError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.subject\n\t\t\t\t\t\t? h('p', { style: styles.previewSubject }, preview.value.subject)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.html\n\t\t\t\t\t\t? // sandbox= — no scripts, no same-origin. Operator-authored HTML\n\t\t\t\t\t\t\t// must never execute in the dashboard's origin, where the admin\n\t\t\t\t\t\t\t// token lives.\n\t\t\t\t\t\t\th('iframe', {\n\t\t\t\t\t\t\t\ttitle: 'Template preview',\n\t\t\t\t\t\t\t\tstyle: styles.previewFrame,\n\t\t\t\t\t\t\t\tsandbox: '',\n\t\t\t\t\t\t\t\tsrcdoc: preview.value.html,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: preview.value\n\t\t\t\t\t\t\t? h('pre', { style: styles.previewText }, preview.value.text)\n\t\t\t\t\t\t\t: h('p', { style: styles.meta }, 'Nothing rendered yet.'),\n\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\trevisions.value.length > 0\n\t\t\t\t\t? h('div', { style: styles.revisions }, [\n\t\t\t\t\t\t\th('h2', { style: styles.subtitle }, 'History'),\n\t\t\t\t\t\t\th('ul', { style: styles.list }, revisions.value.map(renderRevision)),\n\t\t\t\t\t\t])\n\t\t\t\t\t: null,\n\t\t\t]);\n\t\t};\n\t},\n});\n","import type { CSSProperties } from 'vue';\n\nexport const styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '24px', maxWidth: '1180px' },\n\tsplit: { display: 'flex', gap: '24px', alignItems: 'flex-start', flexWrap: 'wrap' },\n\tcolumn: { flex: '1 1 460px', minWidth: '320px', display: 'flex', flexDirection: 'column' },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: '12px',\n\t},\n\tpreviewSubject: { fontSize: '14px', fontWeight: '600', margin: '8px 0' },\n\tpreviewFrame: {\n\t\twidth: '100%',\n\t\theight: '520px',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\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: '8px',\n\t\tpadding: '12px',\n\t\tfontSize: '13px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: { fontSize: '24px', fontWeight: 700 },\n\tsubtitle: { fontSize: '16px', fontWeight: 600, marginBottom: '8px' },\n\tmeta: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: '16px' },\n\tstatus: { padding: '24px', textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: '12px', fontSize: '14px' },\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: '14px', fontWeight: 600, flex: '1' },\n\tlocale: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginRight: '16px' },\n\tactive: { fontSize: '12px', color: 'var(--fonderie-accent-strong,#009767)' },\n\tinactive: { fontSize: '12px', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\tform: { display: 'flex', flexDirection: 'column', gap: '4px' },\n\tlabel: { fontSize: '13px', fontWeight: 600, marginTop: '12px' },\n\tinput: { border: '1px solid var(--fonderie-border,#e0e0e0)', borderRadius: '8px', padding: '10px', fontSize: '14px' },\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '10px',\n\t\tfontSize: '14px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: {\n\t\tdisplay: 'flex',\n\t\talignItems: 'center',\n\t\tgap: '8px',\n\t\tmarginTop: '12px',\n\t\tfontSize: '14px',\n\t},\n\tbutton: {\n\t\tmarginTop: '16px',\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: '8px',\n\t\tborder: 'none',\n\t\tfontSize: '14px',\n\t\tfontWeight: 600,\n\t\tcursor: 'pointer',\n\t\talignSelf: 'flex-start',\n\t},\n\trevisions: { marginTop: '32px' },\n\trevisionRow: {\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: '13px',\n\t},\n\trollbackButton: {\n\t\tbackground: 'none',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '4px 10px',\n\t\tfontSize: '12px',\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { defineComponent, h } from 'vue';\nimport { styles } from '../styles';\n\nexport const TemplateListScreen = defineComponent({\n\tname: 'FonderieTemplateListScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t},\n\temits: {\n\t\t'select-template': (_template: ITemplateEntry) => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { templates, isLoading, error } = useTemplates(props.client);\n\n\t\tfunction renderRow(template: ITemplateEntry) {\n\t\t\treturn h('li', { key: `${template.type}:${template.locale ?? 'base'}`, style: styles.row }, [\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\tstyle: styles.rowButton,\n\t\t\t\t\t\tonClick: () => emit('select-template', template),\n\t\t\t\t\t},\n\t\t\t\t\t[\n\t\t\t\t\t\th('span', { style: styles.type }, template.type),\n\t\t\t\t\t\th('span', { style: styles.locale }, template.locale ?? 'base'),\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t{ style: template.active ? styles.active : styles.inactive },\n\t\t\t\t\t\t\ttemplate.active ? 'Active' : 'Inactive',\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading templates…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, 'Templates'),\n\t\t\t\th('ul', { style: styles.list }, templates.value.map(renderRow)),\n\t\t\t]);\n\t\t};\n\t},\n});\n"],"mappings":"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","names":["import_vue_courier_admin","import_vue"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/screens/TemplateEditorScreen.ts","../src/styles.ts","../src/screens/TemplateListScreen.ts"],"sourcesContent":["export { TemplateEditorScreen, TemplateListScreen } from './screens';\n","import type {\n\tCourierAdminClient,\n\tFonderieApiError,\n\tISetTemplateInput,\n\tITemplateRevision,\n} from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { computed, defineComponent, h, ref, watch } from 'vue';\n\nconst PREVIEW_DEBOUNCE_MS = 500;\nimport { styles } from '../styles';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nexport const TemplateEditorScreen = defineComponent({\n\tname: 'FonderieTemplateEditorScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t\ttype: { type: String, required: true },\n\t\tlocale: { type: String as PropType<string | null>, default: null },\n\t},\n\temits: {\n\t\tsaved: () => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { template, isLoading, error, refresh } = useTemplate(\n\t\t\tprops.client,\n\t\t\tprops.type,\n\t\t\tprops.locale,\n\t\t);\n\t\t// Saves go through the list composable (which re-fetches the template list\n\t\t// after each write); mounting it adds a template-list fetch to this\n\t\t// single-template editor — acceptable for an admin dashboard. Its\n\t\t// isLoading/error track that list fetch, so the save action keeps local state.\n\t\tconst { saveTemplate } = useTemplates(props.client);\n\t\tconst { revisions, rollback } = useTemplateRevisions(props.client, props.type, props.locale);\n\t\tconst isSaving = ref(false);\n\t\tconst saveError = ref<FonderieApiError | null>(null);\n\n\t\tconst subject = ref('');\n\t\tconst html = ref('');\n\t\tconst text = ref('');\n\t\tconst active = ref(true);\n\n\t\twatch(template, (tpl) => {\n\t\t\tif (!tpl) return;\n\t\t\tsubject.value = tpl.subject ?? '';\n\t\t\thtml.value = tpl.html ?? '';\n\t\t\ttext.value = tpl.text;\n\t\t\tactive.value = tpl.active;\n\t\t});\n\n\t\tconst {\n\t\t\tpreview,\n\t\t\tisPreviewing,\n\t\t\terror: previewError,\n\t\t\trenderPreview,\n\t\t} = useTemplatePreview(props.client);\n\t\t// Sample values as JSON text rather than an object, so a half-typed value\n\t\t// does not have to parse on every keystroke.\n\t\tconst sampleJson = ref('{}');\n\t\tconst sampleError = ref<string | null>(null);\n\n\t\tasync function run() {\n\t\t\tlet data: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(sampleJson.value || '{}');\n\t\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\t\tsampleError.value = 'Sample data must be a JSON object.';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\t\tsampleError.value = null;\n\t\t\t} catch {\n\t\t\t\tsampleError.value = 'Sample data is not valid JSON.';\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = await renderPreview(\n\t\t\t\tprops.type,\n\t\t\t\t{\n\t\t\t\t\ttext: text.value,\n\t\t\t\t\tdata,\n\t\t\t\t\t...(subject.value ? { subject: subject.value } : {}),\n\t\t\t\t\t...(html.value ? { html: html.value } : {}),\n\t\t\t\t},\n\t\t\t\tprops.locale,\n\t\t\t);\n\t\t\t// The server reports which variables this content uses; seed the ones\n\t\t\t// with no value yet with their own name so the render shows where each\n\t\t\t// lands. Never overwrite something already typed.\n\t\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\t\tif (missing.length > 0) {\n\t\t\t\tsampleJson.value = JSON.stringify(\n\t\t\t\t\t{ ...data, ...Object.fromEntries(missing.map((v) => [v, v])) },\n\t\t\t\t\tnull,\n\t\t\t\t\t2,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// The preview is always live: it renders once the template loads, then\n\t\t// again PREVIEW_DEBOUNCE_MS after the last edit to the content or the sample\n\t\t// data — debounced, so typing a paragraph costs one request, not one per key.\n\t\tlet previewTimer: ReturnType<typeof setTimeout> | undefined;\n\t\twatch([template, subject, html, text, sampleJson], () => {\n\t\t\tif (!template.value) return;\n\t\t\tclearTimeout(previewTimer);\n\t\t\tpreviewTimer = setTimeout(() => void run(), PREVIEW_DEBOUNCE_MS);\n\t\t});\n\n\t\t// Saving what is already stored would only mint an identical version (the\n\t\t// server treats it as a no-op anyway), so Save waits for a real change.\n\t\tconst dirty = computed(() => {\n\t\t\tconst tpl = template.value;\n\t\t\treturn (\n\t\t\t\t!!tpl &&\n\t\t\t\t(subject.value !== (tpl.subject ?? '') ||\n\t\t\t\t\thtml.value !== (tpl.html ?? '') ||\n\t\t\t\t\ttext.value !== tpl.text ||\n\t\t\t\t\tactive.value !== tpl.active)\n\t\t\t);\n\t\t});\n\n\t\tasync function handleSubmit(event: Event) {\n\t\t\tevent.preventDefault();\n\t\t\tisSaving.value = true;\n\t\t\tsaveError.value = null;\n\t\t\ttry {\n\t\t\t\tconst input: ISetTemplateInput = { text: text.value, active: active.value };\n\t\t\t\tif (subject.value) input.subject = subject.value;\n\t\t\t\tif (html.value) input.html = html.value;\n\t\t\t\tif (template.value?.version !== undefined) input.ifVersion = template.value.version;\n\t\t\t\tawait saveTemplate(props.type, input, props.locale);\n\t\t\t\t// The list composable refreshes its own list; this screen renders the\n\t\t\t\t// single template, so re-read it too.\n\t\t\t\tawait refresh();\n\t\t\t\temit('saved');\n\t\t\t} catch (err) {\n\t\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\t\tsaveError.value = err as FonderieApiError;\n\t\t\t} finally {\n\t\t\t\tisSaving.value = false;\n\t\t\t}\n\t\t}\n\n\t\tfunction renderRevision(rev: ITemplateRevision) {\n\t\t\treturn h('li', { key: rev.version, style: styles.revisionRow }, [\n\t\t\t\th(\n\t\t\t\t\t'span',\n\t\t\t\t\t`v${rev.version} — ${rev.actor ?? 'unknown'} — ${new Date(rev.createdAt).toLocaleString()}`,\n\t\t\t\t),\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{ type: 'button', style: styles.rollbackButton, onClick: () => rollback(rev.version) },\n\t\t\t\t\t'Roll back',\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading template…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, props.type),\n\t\t\t\th(\n\t\t\t\t\t'p',\n\t\t\t\t\t{ style: styles.meta },\n\t\t\t\t\t`${props.locale ?? 'base'} · v${template.value?.version ?? 1}`,\n\t\t\t\t),\n\t\t\t\th('div', { style: styles.split }, [\n\t\t\t\th('div', { style: styles.column }, [\n\t\t\t\th('form', { style: styles.form, onSubmit: handleSubmit }, [\n\t\t\t\t\th('label', { style: styles.label, for: 'template-subject' }, 'Subject'),\n\t\t\t\t\th('input', {\n\t\t\t\t\t\tid: 'template-subject',\n\t\t\t\t\t\tstyle: styles.input,\n\t\t\t\t\t\tvalue: subject.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\tsubject.value = (e.target as HTMLInputElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-html' }, 'HTML body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-html',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 8,\n\t\t\t\t\t\tvalue: html.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\thtml.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-text' }, 'Plain-text body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-text',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 4,\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tvalue: text.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\ttext.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.checkboxLabel }, [\n\t\t\t\t\t\th('input', {\n\t\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\t\tchecked: active.value,\n\t\t\t\t\t\t\tonChange: (e: Event) => {\n\t\t\t\t\t\t\t\tactive.value = (e.target as HTMLInputElement).checked;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t'Active',\n\t\t\t\t\t]),\n\t\t\t\t\tsaveError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, saveError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\th('div', { style: styles.saveRow }, [\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'button',\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'submit',\n\t\t\t\t\t\t\t\tdisabled: isSaving.value || !dirty.value,\n\t\t\t\t\t\t\t\tstyle: dirty.value ? styles.button : styles.buttonDisabled,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tisSaving.value ? 'Saving…' : 'Save',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t!dirty.value && !isSaving.value ? h('span', { style: styles.meta }, 'No changes to save') : null,\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\th('label', { style: styles.label, for: 'template-sample' }, 'Sample data'),\n\t\t\t\th('textarea', {\n\t\t\t\t\tid: 'template-sample',\n\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\trows: 6,\n\t\t\t\t\tspellcheck: false,\n\t\t\t\t\tvalue: sampleJson.value,\n\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\tsampleJson.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tsampleError.value\n\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, sampleError.value)\n\t\t\t\t\t: null,\n\t\t\t\t]),\n\t\t\t\th('div', { style: styles.previewColumn }, [\n\t\t\t\t\th('div', { style: styles.previewHeader }, [\n\t\t\t\t\t\th('span', { style: styles.label }, 'Preview'),\n\t\t\t\t\t\th('span', { style: styles.meta }, isPreviewing.value ? 'Rendering…' : 'Live'),\n\t\t\t\t\t]),\n\t\t\t\t\tpreviewError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, previewError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.subject\n\t\t\t\t\t\t? h('p', { style: styles.previewSubject }, preview.value.subject)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.html\n\t\t\t\t\t\t? // sandbox= — no scripts, no same-origin. Operator-authored HTML\n\t\t\t\t\t\t\t// must never execute in the dashboard's origin, where the admin\n\t\t\t\t\t\t\t// token lives.\n\t\t\t\t\t\t\th('iframe', {\n\t\t\t\t\t\t\t\ttitle: 'Template preview',\n\t\t\t\t\t\t\t\tstyle: styles.previewFrame,\n\t\t\t\t\t\t\t\tsandbox: '',\n\t\t\t\t\t\t\t\tsrcdoc: preview.value.html,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: preview.value\n\t\t\t\t\t\t\t? h('pre', { style: styles.previewText }, preview.value.text)\n\t\t\t\t\t\t\t: h('p', { style: styles.meta }, 'Nothing rendered yet.'),\n\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\trevisions.value.length > 0\n\t\t\t\t\t? h('div', { style: styles.revisions }, [\n\t\t\t\t\t\t\th('h2', { style: styles.subtitle }, 'History'),\n\t\t\t\t\t\t\th('ul', { style: styles.list }, revisions.value.map(renderRevision)),\n\t\t\t\t\t\t])\n\t\t\t\t\t: null,\n\t\t\t]);\n\t\t};\n\t},\n});\n","import type { CSSProperties } from 'vue';\n\nexport const styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '24px' },\n\t// Two columns, always: editor left, rendered result right. Stacking the\n\t// preview under a long form hid it below the fold.\n\tsplit: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: '24px', 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: '16px' },\n\tsaveRow: { display: 'flex', alignItems: 'center', gap: '12px', marginTop: '16px' },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: '12px',\n\t},\n\tpreviewSubject: { fontSize: '14px', fontWeight: '600', margin: '8px 0' },\n\tpreviewFrame: {\n\t\twidth: '100%',\n\t\theight: '520px',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\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: '8px',\n\t\tpadding: '12px',\n\t\tfontSize: '13px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: { fontSize: '24px', fontWeight: 700 },\n\tsubtitle: { fontSize: '16px', fontWeight: 600, marginBottom: '8px' },\n\tmeta: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: '16px' },\n\tstatus: { padding: '24px', textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: '12px', fontSize: '14px' },\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: '14px', fontWeight: 600, flex: '1' },\n\tlocale: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginRight: '16px' },\n\tactive: { fontSize: '12px', color: 'var(--fonderie-accent-strong,#009767)' },\n\tinactive: { fontSize: '12px', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\tform: { display: 'flex', flexDirection: 'column', gap: '4px' },\n\tlabel: { fontSize: '13px', fontWeight: 600, marginTop: '12px' },\n\tinput: { border: '1px solid var(--fonderie-border,#e0e0e0)', borderRadius: '8px', padding: '10px', fontSize: '14px' },\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '10px',\n\t\tfontSize: '14px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: {\n\t\tdisplay: 'flex',\n\t\talignItems: 'center',\n\t\tgap: '8px',\n\t\tmarginTop: '12px',\n\t\tfontSize: '14px',\n\t},\n\tbutton: {\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: '8px',\n\t\tborder: 'none',\n\t\tfontSize: '14px',\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: '8px',\n\t\tborder: 'none',\n\t\tfontSize: '14px',\n\t\tfontWeight: 600,\n\t\tcursor: 'not-allowed',\n\t\talignSelf: 'flex-start',\n\t},\n\trevisions: { marginTop: '32px' },\n\trevisionRow: {\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: '13px',\n\t},\n\trollbackButton: {\n\t\tbackground: 'none',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '4px 10px',\n\t\tfontSize: '12px',\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { defineComponent, h } from 'vue';\nimport { styles } from '../styles';\n\nexport const TemplateListScreen = defineComponent({\n\tname: 'FonderieTemplateListScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t},\n\temits: {\n\t\t'select-template': (_template: ITemplateEntry) => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { templates, isLoading, error } = useTemplates(props.client);\n\n\t\tfunction renderRow(template: ITemplateEntry) {\n\t\t\treturn h('li', { key: `${template.type}:${template.locale ?? 'base'}`, style: styles.row }, [\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\tstyle: styles.rowButton,\n\t\t\t\t\t\tonClick: () => emit('select-template', template),\n\t\t\t\t\t},\n\t\t\t\t\t[\n\t\t\t\t\t\th('span', { style: styles.type }, template.type),\n\t\t\t\t\t\th('span', { style: styles.locale }, template.locale ?? 'base'),\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t{ style: template.active ? styles.active : styles.inactive },\n\t\t\t\t\t\t\ttemplate.active ? 'Active' : 'Inactive',\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading templates…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, 'Templates'),\n\t\t\t\th('ul', { style: styles.list }, templates.value.map(renderRow)),\n\t\t\t]);\n\t\t};\n\t},\n});\n"],"mappings":"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","names":["import_vue_courier_admin","import_vue"]}
package/dist/index.js CHANGED
@@ -5,13 +5,18 @@ import {
5
5
  useTemplateRevisions,
6
6
  useTemplates
7
7
  } from "@fonderie/vue-courier-admin";
8
- import { defineComponent, h, ref, watch } from "vue";
8
+ import { computed, defineComponent, h, ref, watch } from "vue";
9
9
 
10
10
  // src/styles.ts
11
11
  var styles = {
12
- container: { padding: "24px", maxWidth: "1180px" },
13
- split: { display: "flex", gap: "24px", alignItems: "flex-start", flexWrap: "wrap" },
14
- column: { flex: "1 1 460px", minWidth: "320px", display: "flex", flexDirection: "column" },
12
+ container: { padding: "24px" },
13
+ // Two columns, always: editor left, rendered result right. Stacking the
14
+ // preview under a long form hid it below the fold.
15
+ split: { display: "grid", gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)", gap: "24px", alignItems: "start" },
16
+ column: { minWidth: "0", display: "flex", flexDirection: "column" },
17
+ // Pinned while the form scrolls, so the preview stays in view as you edit.
18
+ previewColumn: { minWidth: "0", display: "flex", flexDirection: "column", position: "sticky", top: "16px" },
19
+ saveRow: { display: "flex", alignItems: "center", gap: "12px", marginTop: "16px" },
15
20
  previewHeader: {
16
21
  display: "flex",
17
22
  justifyContent: "space-between",
@@ -74,7 +79,6 @@ var styles = {
74
79
  fontSize: "14px"
75
80
  },
76
81
  button: {
77
- marginTop: "16px",
78
82
  backgroundColor: "var(--fonderie-text,#171717)",
79
83
  color: "var(--fonderie-surface,#fff)",
80
84
  padding: "10px 20px",
@@ -85,6 +89,17 @@ var styles = {
85
89
  cursor: "pointer",
86
90
  alignSelf: "flex-start"
87
91
  },
92
+ buttonDisabled: {
93
+ backgroundColor: "var(--fonderie-border,#e0e0e0)",
94
+ color: "var(--fonderie-text-muted,#5c5c5c)",
95
+ padding: "10px 20px",
96
+ borderRadius: "8px",
97
+ border: "none",
98
+ fontSize: "14px",
99
+ fontWeight: 600,
100
+ cursor: "not-allowed",
101
+ alignSelf: "flex-start"
102
+ },
88
103
  revisions: { marginTop: "32px" },
89
104
  revisionRow: {
90
105
  display: "flex",
@@ -105,6 +120,7 @@ var styles = {
105
120
  };
106
121
 
107
122
  // src/screens/TemplateEditorScreen.ts
123
+ var PREVIEW_DEBOUNCE_MS = 500;
108
124
  var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
109
125
  var TemplateEditorScreen = defineComponent({
110
126
  name: "FonderieTemplateEditorScreen",
@@ -178,8 +194,15 @@ var TemplateEditorScreen = defineComponent({
178
194
  );
179
195
  }
180
196
  }
181
- watch(template, (tpl) => {
182
- if (tpl) void run();
197
+ let previewTimer;
198
+ watch([template, subject, html, text, sampleJson], () => {
199
+ if (!template.value) return;
200
+ clearTimeout(previewTimer);
201
+ previewTimer = setTimeout(() => void run(), PREVIEW_DEBOUNCE_MS);
202
+ });
203
+ const dirty = computed(() => {
204
+ const tpl = template.value;
205
+ return !!tpl && (subject.value !== (tpl.subject ?? "") || html.value !== (tpl.html ?? "") || text.value !== tpl.text || active.value !== tpl.active);
183
206
  });
184
207
  async function handleSubmit(event) {
185
208
  event.preventDefault();
@@ -267,11 +290,18 @@ var TemplateEditorScreen = defineComponent({
267
290
  "Active"
268
291
  ]),
269
292
  saveError.value ? h("p", { style: styles.error, role: "alert" }, saveError.value.explanation) : null,
270
- h(
271
- "button",
272
- { type: "submit", disabled: isSaving.value, style: styles.button },
273
- isSaving.value ? "Saving\u2026" : "Save"
274
- )
293
+ h("div", { style: styles.saveRow }, [
294
+ h(
295
+ "button",
296
+ {
297
+ type: "submit",
298
+ disabled: isSaving.value || !dirty.value,
299
+ style: dirty.value ? styles.button : styles.buttonDisabled
300
+ },
301
+ isSaving.value ? "Saving\u2026" : "Save"
302
+ ),
303
+ !dirty.value && !isSaving.value ? h("span", { style: styles.meta }, "No changes to save") : null
304
+ ])
275
305
  ]),
276
306
  h("label", { style: styles.label, for: "template-sample" }, "Sample data"),
277
307
  h("textarea", {
@@ -286,19 +316,10 @@ var TemplateEditorScreen = defineComponent({
286
316
  }),
287
317
  sampleError.value ? h("p", { style: styles.error, role: "alert" }, sampleError.value) : null
288
318
  ]),
289
- h("div", { style: styles.column }, [
319
+ h("div", { style: styles.previewColumn }, [
290
320
  h("div", { style: styles.previewHeader }, [
291
321
  h("span", { style: styles.label }, "Preview"),
292
- h(
293
- "button",
294
- {
295
- type: "button",
296
- style: styles.rollbackButton,
297
- disabled: isPreviewing.value,
298
- onClick: () => void run()
299
- },
300
- isPreviewing.value ? "Rendering\u2026" : "Render"
301
- )
322
+ h("span", { style: styles.meta }, isPreviewing.value ? "Rendering\u2026" : "Live")
302
323
  ]),
303
324
  previewError.value ? h("p", { style: styles.error, role: "alert" }, previewError.value.explanation) : null,
304
325
  preview.value?.subject ? h("p", { style: styles.previewSubject }, preview.value.subject) : null,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/screens/TemplateEditorScreen.ts","../src/styles.ts","../src/screens/TemplateListScreen.ts"],"sourcesContent":["import type {\n\tCourierAdminClient,\n\tFonderieApiError,\n\tISetTemplateInput,\n\tITemplateRevision,\n} from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { defineComponent, h, ref, watch } from 'vue';\nimport { styles } from '../styles';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nexport const TemplateEditorScreen = defineComponent({\n\tname: 'FonderieTemplateEditorScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t\ttype: { type: String, required: true },\n\t\tlocale: { type: String as PropType<string | null>, default: null },\n\t},\n\temits: {\n\t\tsaved: () => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { template, isLoading, error, refresh } = useTemplate(\n\t\t\tprops.client,\n\t\t\tprops.type,\n\t\t\tprops.locale,\n\t\t);\n\t\t// Saves go through the list composable (which re-fetches the template list\n\t\t// after each write); mounting it adds a template-list fetch to this\n\t\t// single-template editor — acceptable for an admin dashboard. Its\n\t\t// isLoading/error track that list fetch, so the save action keeps local state.\n\t\tconst { saveTemplate } = useTemplates(props.client);\n\t\tconst { revisions, rollback } = useTemplateRevisions(props.client, props.type, props.locale);\n\t\tconst isSaving = ref(false);\n\t\tconst saveError = ref<FonderieApiError | null>(null);\n\n\t\tconst subject = ref('');\n\t\tconst html = ref('');\n\t\tconst text = ref('');\n\t\tconst active = ref(true);\n\n\t\twatch(template, (tpl) => {\n\t\t\tif (!tpl) return;\n\t\t\tsubject.value = tpl.subject ?? '';\n\t\t\thtml.value = tpl.html ?? '';\n\t\t\ttext.value = tpl.text;\n\t\t\tactive.value = tpl.active;\n\t\t});\n\n\t\tconst {\n\t\t\tpreview,\n\t\t\tisPreviewing,\n\t\t\terror: previewError,\n\t\t\trenderPreview,\n\t\t} = useTemplatePreview(props.client);\n\t\t// Sample values as JSON text rather than an object, so a half-typed value\n\t\t// does not have to parse on every keystroke.\n\t\tconst sampleJson = ref('{}');\n\t\tconst sampleError = ref<string | null>(null);\n\n\t\tasync function run() {\n\t\t\tlet data: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(sampleJson.value || '{}');\n\t\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\t\tsampleError.value = 'Sample data must be a JSON object.';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\t\tsampleError.value = null;\n\t\t\t} catch {\n\t\t\t\tsampleError.value = 'Sample data is not valid JSON.';\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = await renderPreview(\n\t\t\t\tprops.type,\n\t\t\t\t{\n\t\t\t\t\ttext: text.value,\n\t\t\t\t\tdata,\n\t\t\t\t\t...(subject.value ? { subject: subject.value } : {}),\n\t\t\t\t\t...(html.value ? { html: html.value } : {}),\n\t\t\t\t},\n\t\t\t\tprops.locale,\n\t\t\t);\n\t\t\t// The server reports which variables this content uses; seed the ones\n\t\t\t// with no value yet with their own name so the render shows where each\n\t\t\t// lands. Never overwrite something already typed.\n\t\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\t\tif (missing.length > 0) {\n\t\t\t\tsampleJson.value = JSON.stringify(\n\t\t\t\t\t{ ...data, ...Object.fromEntries(missing.map((v) => [v, v])) },\n\t\t\t\t\tnull,\n\t\t\t\t\t2,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// One render once the template has loaded, so the pane is never empty on\n\t\t// arrival. After that it is explicit — a request per keystroke is not a\n\t\t// preview, it is a load test.\n\t\twatch(template, (tpl) => {\n\t\t\tif (tpl) void run();\n\t\t});\n\n\t\tasync function handleSubmit(event: Event) {\n\t\t\tevent.preventDefault();\n\t\t\tisSaving.value = true;\n\t\t\tsaveError.value = null;\n\t\t\ttry {\n\t\t\t\tconst input: ISetTemplateInput = { text: text.value, active: active.value };\n\t\t\t\tif (subject.value) input.subject = subject.value;\n\t\t\t\tif (html.value) input.html = html.value;\n\t\t\t\tif (template.value?.version !== undefined) input.ifVersion = template.value.version;\n\t\t\t\tawait saveTemplate(props.type, input, props.locale);\n\t\t\t\t// The list composable refreshes its own list; this screen renders the\n\t\t\t\t// single template, so re-read it too.\n\t\t\t\tawait refresh();\n\t\t\t\temit('saved');\n\t\t\t} catch (err) {\n\t\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\t\tsaveError.value = err as FonderieApiError;\n\t\t\t} finally {\n\t\t\t\tisSaving.value = false;\n\t\t\t}\n\t\t}\n\n\t\tfunction renderRevision(rev: ITemplateRevision) {\n\t\t\treturn h('li', { key: rev.version, style: styles.revisionRow }, [\n\t\t\t\th(\n\t\t\t\t\t'span',\n\t\t\t\t\t`v${rev.version} — ${rev.actor ?? 'unknown'} — ${new Date(rev.createdAt).toLocaleString()}`,\n\t\t\t\t),\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{ type: 'button', style: styles.rollbackButton, onClick: () => rollback(rev.version) },\n\t\t\t\t\t'Roll back',\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading template…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, props.type),\n\t\t\t\th(\n\t\t\t\t\t'p',\n\t\t\t\t\t{ style: styles.meta },\n\t\t\t\t\t`${props.locale ?? 'base'} · v${template.value?.version ?? 1}`,\n\t\t\t\t),\n\t\t\t\th('div', { style: styles.split }, [\n\t\t\t\th('div', { style: styles.column }, [\n\t\t\t\th('form', { style: styles.form, onSubmit: handleSubmit }, [\n\t\t\t\t\th('label', { style: styles.label, for: 'template-subject' }, 'Subject'),\n\t\t\t\t\th('input', {\n\t\t\t\t\t\tid: 'template-subject',\n\t\t\t\t\t\tstyle: styles.input,\n\t\t\t\t\t\tvalue: subject.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\tsubject.value = (e.target as HTMLInputElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-html' }, 'HTML body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-html',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 8,\n\t\t\t\t\t\tvalue: html.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\thtml.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-text' }, 'Plain-text body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-text',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 4,\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tvalue: text.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\ttext.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.checkboxLabel }, [\n\t\t\t\t\t\th('input', {\n\t\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\t\tchecked: active.value,\n\t\t\t\t\t\t\tonChange: (e: Event) => {\n\t\t\t\t\t\t\t\tactive.value = (e.target as HTMLInputElement).checked;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t'Active',\n\t\t\t\t\t]),\n\t\t\t\t\tsaveError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, saveError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\th(\n\t\t\t\t\t\t'button',\n\t\t\t\t\t\t{ type: 'submit', disabled: isSaving.value, style: styles.button },\n\t\t\t\t\t\tisSaving.value ? 'Saving…' : 'Save',\n\t\t\t\t\t),\n\t\t\t\t]),\n\t\t\t\th('label', { style: styles.label, for: 'template-sample' }, 'Sample data'),\n\t\t\t\th('textarea', {\n\t\t\t\t\tid: 'template-sample',\n\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\trows: 6,\n\t\t\t\t\tspellcheck: false,\n\t\t\t\t\tvalue: sampleJson.value,\n\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\tsampleJson.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tsampleError.value\n\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, sampleError.value)\n\t\t\t\t\t: null,\n\t\t\t\t]),\n\t\t\t\th('div', { style: styles.column }, [\n\t\t\t\t\th('div', { style: styles.previewHeader }, [\n\t\t\t\t\t\th('span', { style: styles.label }, 'Preview'),\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'button',\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t\t\tstyle: styles.rollbackButton,\n\t\t\t\t\t\t\t\tdisabled: isPreviewing.value,\n\t\t\t\t\t\t\t\tonClick: () => void run(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tisPreviewing.value ? 'Rendering…' : 'Render',\n\t\t\t\t\t\t),\n\t\t\t\t\t]),\n\t\t\t\t\tpreviewError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, previewError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.subject\n\t\t\t\t\t\t? h('p', { style: styles.previewSubject }, preview.value.subject)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.html\n\t\t\t\t\t\t? // sandbox= — no scripts, no same-origin. Operator-authored HTML\n\t\t\t\t\t\t\t// must never execute in the dashboard's origin, where the admin\n\t\t\t\t\t\t\t// token lives.\n\t\t\t\t\t\t\th('iframe', {\n\t\t\t\t\t\t\t\ttitle: 'Template preview',\n\t\t\t\t\t\t\t\tstyle: styles.previewFrame,\n\t\t\t\t\t\t\t\tsandbox: '',\n\t\t\t\t\t\t\t\tsrcdoc: preview.value.html,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: preview.value\n\t\t\t\t\t\t\t? h('pre', { style: styles.previewText }, preview.value.text)\n\t\t\t\t\t\t\t: h('p', { style: styles.meta }, 'Nothing rendered yet.'),\n\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\trevisions.value.length > 0\n\t\t\t\t\t? h('div', { style: styles.revisions }, [\n\t\t\t\t\t\t\th('h2', { style: styles.subtitle }, 'History'),\n\t\t\t\t\t\t\th('ul', { style: styles.list }, revisions.value.map(renderRevision)),\n\t\t\t\t\t\t])\n\t\t\t\t\t: null,\n\t\t\t]);\n\t\t};\n\t},\n});\n","import type { CSSProperties } from 'vue';\n\nexport const styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '24px', maxWidth: '1180px' },\n\tsplit: { display: 'flex', gap: '24px', alignItems: 'flex-start', flexWrap: 'wrap' },\n\tcolumn: { flex: '1 1 460px', minWidth: '320px', display: 'flex', flexDirection: 'column' },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: '12px',\n\t},\n\tpreviewSubject: { fontSize: '14px', fontWeight: '600', margin: '8px 0' },\n\tpreviewFrame: {\n\t\twidth: '100%',\n\t\theight: '520px',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\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: '8px',\n\t\tpadding: '12px',\n\t\tfontSize: '13px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: { fontSize: '24px', fontWeight: 700 },\n\tsubtitle: { fontSize: '16px', fontWeight: 600, marginBottom: '8px' },\n\tmeta: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: '16px' },\n\tstatus: { padding: '24px', textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: '12px', fontSize: '14px' },\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: '14px', fontWeight: 600, flex: '1' },\n\tlocale: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginRight: '16px' },\n\tactive: { fontSize: '12px', color: 'var(--fonderie-accent-strong,#009767)' },\n\tinactive: { fontSize: '12px', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\tform: { display: 'flex', flexDirection: 'column', gap: '4px' },\n\tlabel: { fontSize: '13px', fontWeight: 600, marginTop: '12px' },\n\tinput: { border: '1px solid var(--fonderie-border,#e0e0e0)', borderRadius: '8px', padding: '10px', fontSize: '14px' },\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '10px',\n\t\tfontSize: '14px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: {\n\t\tdisplay: 'flex',\n\t\talignItems: 'center',\n\t\tgap: '8px',\n\t\tmarginTop: '12px',\n\t\tfontSize: '14px',\n\t},\n\tbutton: {\n\t\tmarginTop: '16px',\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: '8px',\n\t\tborder: 'none',\n\t\tfontSize: '14px',\n\t\tfontWeight: 600,\n\t\tcursor: 'pointer',\n\t\talignSelf: 'flex-start',\n\t},\n\trevisions: { marginTop: '32px' },\n\trevisionRow: {\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: '13px',\n\t},\n\trollbackButton: {\n\t\tbackground: 'none',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '4px 10px',\n\t\tfontSize: '12px',\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { defineComponent, h } from 'vue';\nimport { styles } from '../styles';\n\nexport const TemplateListScreen = defineComponent({\n\tname: 'FonderieTemplateListScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t},\n\temits: {\n\t\t'select-template': (_template: ITemplateEntry) => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { templates, isLoading, error } = useTemplates(props.client);\n\n\t\tfunction renderRow(template: ITemplateEntry) {\n\t\t\treturn h('li', { key: `${template.type}:${template.locale ?? 'base'}`, style: styles.row }, [\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\tstyle: styles.rowButton,\n\t\t\t\t\t\tonClick: () => emit('select-template', template),\n\t\t\t\t\t},\n\t\t\t\t\t[\n\t\t\t\t\t\th('span', { style: styles.type }, template.type),\n\t\t\t\t\t\th('span', { style: styles.locale }, template.locale ?? 'base'),\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t{ style: template.active ? styles.active : styles.inactive },\n\t\t\t\t\t\t\ttemplate.active ? 'Active' : 'Inactive',\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading templates…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, 'Templates'),\n\t\t\t\th('ul', { style: styles.list }, templates.value.map(renderRow)),\n\t\t\t]);\n\t\t};\n\t},\n});\n"],"mappings":"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","names":["useTemplates","defineComponent","h","defineComponent","useTemplates","h"]}
1
+ {"version":3,"sources":["../src/screens/TemplateEditorScreen.ts","../src/styles.ts","../src/screens/TemplateListScreen.ts"],"sourcesContent":["import type {\n\tCourierAdminClient,\n\tFonderieApiError,\n\tISetTemplateInput,\n\tITemplateRevision,\n} from '@fonderie/client';\nimport {\n\tuseTemplate,\n\tuseTemplatePreview,\n\tuseTemplateRevisions,\n\tuseTemplates,\n} from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { computed, defineComponent, h, ref, watch } from 'vue';\n\nconst PREVIEW_DEBOUNCE_MS = 500;\nimport { styles } from '../styles';\n\n// Implicit variables the layout injects — an operator never supplies these, so\n// offering them as fields would just be noise.\nconst IMPLICIT = new Set(['subject', 'preheader', 'brandName']);\n\nexport const TemplateEditorScreen = defineComponent({\n\tname: 'FonderieTemplateEditorScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t\ttype: { type: String, required: true },\n\t\tlocale: { type: String as PropType<string | null>, default: null },\n\t},\n\temits: {\n\t\tsaved: () => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { template, isLoading, error, refresh } = useTemplate(\n\t\t\tprops.client,\n\t\t\tprops.type,\n\t\t\tprops.locale,\n\t\t);\n\t\t// Saves go through the list composable (which re-fetches the template list\n\t\t// after each write); mounting it adds a template-list fetch to this\n\t\t// single-template editor — acceptable for an admin dashboard. Its\n\t\t// isLoading/error track that list fetch, so the save action keeps local state.\n\t\tconst { saveTemplate } = useTemplates(props.client);\n\t\tconst { revisions, rollback } = useTemplateRevisions(props.client, props.type, props.locale);\n\t\tconst isSaving = ref(false);\n\t\tconst saveError = ref<FonderieApiError | null>(null);\n\n\t\tconst subject = ref('');\n\t\tconst html = ref('');\n\t\tconst text = ref('');\n\t\tconst active = ref(true);\n\n\t\twatch(template, (tpl) => {\n\t\t\tif (!tpl) return;\n\t\t\tsubject.value = tpl.subject ?? '';\n\t\t\thtml.value = tpl.html ?? '';\n\t\t\ttext.value = tpl.text;\n\t\t\tactive.value = tpl.active;\n\t\t});\n\n\t\tconst {\n\t\t\tpreview,\n\t\t\tisPreviewing,\n\t\t\terror: previewError,\n\t\t\trenderPreview,\n\t\t} = useTemplatePreview(props.client);\n\t\t// Sample values as JSON text rather than an object, so a half-typed value\n\t\t// does not have to parse on every keystroke.\n\t\tconst sampleJson = ref('{}');\n\t\tconst sampleError = ref<string | null>(null);\n\n\t\tasync function run() {\n\t\t\tlet data: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(sampleJson.value || '{}');\n\t\t\t\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\t\tsampleError.value = 'Sample data must be a JSON object.';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdata = parsed as Record<string, unknown>;\n\t\t\t\tsampleError.value = null;\n\t\t\t} catch {\n\t\t\t\tsampleError.value = 'Sample data is not valid JSON.';\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst result = await renderPreview(\n\t\t\t\tprops.type,\n\t\t\t\t{\n\t\t\t\t\ttext: text.value,\n\t\t\t\t\tdata,\n\t\t\t\t\t...(subject.value ? { subject: subject.value } : {}),\n\t\t\t\t\t...(html.value ? { html: html.value } : {}),\n\t\t\t\t},\n\t\t\t\tprops.locale,\n\t\t\t);\n\t\t\t// The server reports which variables this content uses; seed the ones\n\t\t\t// with no value yet with their own name so the render shows where each\n\t\t\t// lands. Never overwrite something already typed.\n\t\t\tconst missing = result.variables.filter((v) => !IMPLICIT.has(v) && !(v in data));\n\t\t\tif (missing.length > 0) {\n\t\t\t\tsampleJson.value = JSON.stringify(\n\t\t\t\t\t{ ...data, ...Object.fromEntries(missing.map((v) => [v, v])) },\n\t\t\t\t\tnull,\n\t\t\t\t\t2,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// The preview is always live: it renders once the template loads, then\n\t\t// again PREVIEW_DEBOUNCE_MS after the last edit to the content or the sample\n\t\t// data — debounced, so typing a paragraph costs one request, not one per key.\n\t\tlet previewTimer: ReturnType<typeof setTimeout> | undefined;\n\t\twatch([template, subject, html, text, sampleJson], () => {\n\t\t\tif (!template.value) return;\n\t\t\tclearTimeout(previewTimer);\n\t\t\tpreviewTimer = setTimeout(() => void run(), PREVIEW_DEBOUNCE_MS);\n\t\t});\n\n\t\t// Saving what is already stored would only mint an identical version (the\n\t\t// server treats it as a no-op anyway), so Save waits for a real change.\n\t\tconst dirty = computed(() => {\n\t\t\tconst tpl = template.value;\n\t\t\treturn (\n\t\t\t\t!!tpl &&\n\t\t\t\t(subject.value !== (tpl.subject ?? '') ||\n\t\t\t\t\thtml.value !== (tpl.html ?? '') ||\n\t\t\t\t\ttext.value !== tpl.text ||\n\t\t\t\t\tactive.value !== tpl.active)\n\t\t\t);\n\t\t});\n\n\t\tasync function handleSubmit(event: Event) {\n\t\t\tevent.preventDefault();\n\t\t\tisSaving.value = true;\n\t\t\tsaveError.value = null;\n\t\t\ttry {\n\t\t\t\tconst input: ISetTemplateInput = { text: text.value, active: active.value };\n\t\t\t\tif (subject.value) input.subject = subject.value;\n\t\t\t\tif (html.value) input.html = html.value;\n\t\t\t\tif (template.value?.version !== undefined) input.ifVersion = template.value.version;\n\t\t\t\tawait saveTemplate(props.type, input, props.locale);\n\t\t\t\t// The list composable refreshes its own list; this screen renders the\n\t\t\t\t// single template, so re-read it too.\n\t\t\t\tawait refresh();\n\t\t\t\temit('saved');\n\t\t\t} catch (err) {\n\t\t\t\t// useTemplates normalizes every failure to FonderieApiError before throwing.\n\t\t\t\tsaveError.value = err as FonderieApiError;\n\t\t\t} finally {\n\t\t\t\tisSaving.value = false;\n\t\t\t}\n\t\t}\n\n\t\tfunction renderRevision(rev: ITemplateRevision) {\n\t\t\treturn h('li', { key: rev.version, style: styles.revisionRow }, [\n\t\t\t\th(\n\t\t\t\t\t'span',\n\t\t\t\t\t`v${rev.version} — ${rev.actor ?? 'unknown'} — ${new Date(rev.createdAt).toLocaleString()}`,\n\t\t\t\t),\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{ type: 'button', style: styles.rollbackButton, onClick: () => rollback(rev.version) },\n\t\t\t\t\t'Roll back',\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading template…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, props.type),\n\t\t\t\th(\n\t\t\t\t\t'p',\n\t\t\t\t\t{ style: styles.meta },\n\t\t\t\t\t`${props.locale ?? 'base'} · v${template.value?.version ?? 1}`,\n\t\t\t\t),\n\t\t\t\th('div', { style: styles.split }, [\n\t\t\t\th('div', { style: styles.column }, [\n\t\t\t\th('form', { style: styles.form, onSubmit: handleSubmit }, [\n\t\t\t\t\th('label', { style: styles.label, for: 'template-subject' }, 'Subject'),\n\t\t\t\t\th('input', {\n\t\t\t\t\t\tid: 'template-subject',\n\t\t\t\t\t\tstyle: styles.input,\n\t\t\t\t\t\tvalue: subject.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\tsubject.value = (e.target as HTMLInputElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-html' }, 'HTML body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-html',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 8,\n\t\t\t\t\t\tvalue: html.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\thtml.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.label, for: 'template-text' }, 'Plain-text body'),\n\t\t\t\t\th('textarea', {\n\t\t\t\t\t\tid: 'template-text',\n\t\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\t\trows: 4,\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tvalue: text.value,\n\t\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\t\ttext.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\th('label', { style: styles.checkboxLabel }, [\n\t\t\t\t\t\th('input', {\n\t\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\t\tchecked: active.value,\n\t\t\t\t\t\t\tonChange: (e: Event) => {\n\t\t\t\t\t\t\t\tactive.value = (e.target as HTMLInputElement).checked;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t'Active',\n\t\t\t\t\t]),\n\t\t\t\t\tsaveError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, saveError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\th('div', { style: styles.saveRow }, [\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'button',\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'submit',\n\t\t\t\t\t\t\t\tdisabled: isSaving.value || !dirty.value,\n\t\t\t\t\t\t\t\tstyle: dirty.value ? styles.button : styles.buttonDisabled,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tisSaving.value ? 'Saving…' : 'Save',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t!dirty.value && !isSaving.value ? h('span', { style: styles.meta }, 'No changes to save') : null,\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\th('label', { style: styles.label, for: 'template-sample' }, 'Sample data'),\n\t\t\t\th('textarea', {\n\t\t\t\t\tid: 'template-sample',\n\t\t\t\t\tstyle: styles.textarea,\n\t\t\t\t\trows: 6,\n\t\t\t\t\tspellcheck: false,\n\t\t\t\t\tvalue: sampleJson.value,\n\t\t\t\t\tonInput: (e: Event) => {\n\t\t\t\t\t\tsampleJson.value = (e.target as HTMLTextAreaElement).value;\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tsampleError.value\n\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, sampleError.value)\n\t\t\t\t\t: null,\n\t\t\t\t]),\n\t\t\t\th('div', { style: styles.previewColumn }, [\n\t\t\t\t\th('div', { style: styles.previewHeader }, [\n\t\t\t\t\t\th('span', { style: styles.label }, 'Preview'),\n\t\t\t\t\t\th('span', { style: styles.meta }, isPreviewing.value ? 'Rendering…' : 'Live'),\n\t\t\t\t\t]),\n\t\t\t\t\tpreviewError.value\n\t\t\t\t\t\t? h('p', { style: styles.error, role: 'alert' }, previewError.value.explanation)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.subject\n\t\t\t\t\t\t? h('p', { style: styles.previewSubject }, preview.value.subject)\n\t\t\t\t\t\t: null,\n\t\t\t\t\tpreview.value?.html\n\t\t\t\t\t\t? // sandbox= — no scripts, no same-origin. Operator-authored HTML\n\t\t\t\t\t\t\t// must never execute in the dashboard's origin, where the admin\n\t\t\t\t\t\t\t// token lives.\n\t\t\t\t\t\t\th('iframe', {\n\t\t\t\t\t\t\t\ttitle: 'Template preview',\n\t\t\t\t\t\t\t\tstyle: styles.previewFrame,\n\t\t\t\t\t\t\t\tsandbox: '',\n\t\t\t\t\t\t\t\tsrcdoc: preview.value.html,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: preview.value\n\t\t\t\t\t\t\t? h('pre', { style: styles.previewText }, preview.value.text)\n\t\t\t\t\t\t\t: h('p', { style: styles.meta }, 'Nothing rendered yet.'),\n\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\trevisions.value.length > 0\n\t\t\t\t\t? h('div', { style: styles.revisions }, [\n\t\t\t\t\t\t\th('h2', { style: styles.subtitle }, 'History'),\n\t\t\t\t\t\t\th('ul', { style: styles.list }, revisions.value.map(renderRevision)),\n\t\t\t\t\t\t])\n\t\t\t\t\t: null,\n\t\t\t]);\n\t\t};\n\t},\n});\n","import type { CSSProperties } from 'vue';\n\nexport const styles: Record<string, CSSProperties> = {\n\tcontainer: { padding: '24px' },\n\t// Two columns, always: editor left, rendered result right. Stacking the\n\t// preview under a long form hid it below the fold.\n\tsplit: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', gap: '24px', 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: '16px' },\n\tsaveRow: { display: 'flex', alignItems: 'center', gap: '12px', marginTop: '16px' },\n\tpreviewHeader: {\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'space-between',\n\t\talignItems: 'center',\n\t\tmarginTop: '12px',\n\t},\n\tpreviewSubject: { fontSize: '14px', fontWeight: '600', margin: '8px 0' },\n\tpreviewFrame: {\n\t\twidth: '100%',\n\t\theight: '520px',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\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: '8px',\n\t\tpadding: '12px',\n\t\tfontSize: '13px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\ttitle: { fontSize: '24px', fontWeight: 700 },\n\tsubtitle: { fontSize: '16px', fontWeight: 600, marginBottom: '8px' },\n\tmeta: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginBottom: '16px' },\n\tstatus: { padding: '24px', textAlign: 'center', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\terror: { color: 'var(--fonderie-danger,#e00)', marginBottom: '12px', fontSize: '14px' },\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: '14px', fontWeight: 600, flex: '1' },\n\tlocale: { fontSize: '13px', color: 'var(--fonderie-text-muted,#5c5c5c)', marginRight: '16px' },\n\tactive: { fontSize: '12px', color: 'var(--fonderie-accent-strong,#009767)' },\n\tinactive: { fontSize: '12px', color: 'var(--fonderie-text-muted,#5c5c5c)' },\n\tform: { display: 'flex', flexDirection: 'column', gap: '4px' },\n\tlabel: { fontSize: '13px', fontWeight: 600, marginTop: '12px' },\n\tinput: { border: '1px solid var(--fonderie-border,#e0e0e0)', borderRadius: '8px', padding: '10px', fontSize: '14px' },\n\ttextarea: {\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '10px',\n\t\tfontSize: '14px',\n\t\tfontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace)',\n\t},\n\tcheckboxLabel: {\n\t\tdisplay: 'flex',\n\t\talignItems: 'center',\n\t\tgap: '8px',\n\t\tmarginTop: '12px',\n\t\tfontSize: '14px',\n\t},\n\tbutton: {\n\t\tbackgroundColor: 'var(--fonderie-text,#171717)',\n\t\tcolor: 'var(--fonderie-surface,#fff)',\n\t\tpadding: '10px 20px',\n\t\tborderRadius: '8px',\n\t\tborder: 'none',\n\t\tfontSize: '14px',\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: '8px',\n\t\tborder: 'none',\n\t\tfontSize: '14px',\n\t\tfontWeight: 600,\n\t\tcursor: 'not-allowed',\n\t\talignSelf: 'flex-start',\n\t},\n\trevisions: { marginTop: '32px' },\n\trevisionRow: {\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: '13px',\n\t},\n\trollbackButton: {\n\t\tbackground: 'none',\n\t\tborder: '1px solid var(--fonderie-border,#e0e0e0)',\n\t\tborderRadius: '8px',\n\t\tpadding: '4px 10px',\n\t\tfontSize: '12px',\n\t\tcursor: 'pointer',\n\t},\n};\n","import type { CourierAdminClient, ITemplateEntry } from '@fonderie/client';\nimport { useTemplates } from '@fonderie/vue-courier-admin';\nimport type { PropType } from 'vue';\nimport { defineComponent, h } from 'vue';\nimport { styles } from '../styles';\n\nexport const TemplateListScreen = defineComponent({\n\tname: 'FonderieTemplateListScreen',\n\tprops: {\n\t\tclient: { type: Object as PropType<CourierAdminClient>, required: true },\n\t},\n\temits: {\n\t\t'select-template': (_template: ITemplateEntry) => true,\n\t},\n\tsetup(props, { emit }) {\n\t\tconst { templates, isLoading, error } = useTemplates(props.client);\n\n\t\tfunction renderRow(template: ITemplateEntry) {\n\t\t\treturn h('li', { key: `${template.type}:${template.locale ?? 'base'}`, style: styles.row }, [\n\t\t\t\th(\n\t\t\t\t\t'button',\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\tstyle: styles.rowButton,\n\t\t\t\t\t\tonClick: () => emit('select-template', template),\n\t\t\t\t\t},\n\t\t\t\t\t[\n\t\t\t\t\t\th('span', { style: styles.type }, template.type),\n\t\t\t\t\t\th('span', { style: styles.locale }, template.locale ?? 'base'),\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t{ style: template.active ? styles.active : styles.inactive },\n\t\t\t\t\t\t\ttemplate.active ? 'Active' : 'Inactive',\n\t\t\t\t\t\t),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t]);\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (isLoading.value) return h('p', { style: styles.status }, 'Loading templates…');\n\t\t\tif (error.value)\n\t\t\t\treturn h('p', { style: styles.error, role: 'alert' }, error.value.explanation);\n\n\t\t\treturn h('div', { style: styles.container }, [\n\t\t\t\th('h1', { style: styles.title }, 'Templates'),\n\t\t\t\th('ul', { style: styles.list }, templates.value.map(renderRow)),\n\t\t\t]);\n\t\t};\n\t},\n});\n"],"mappings":"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","names":["useTemplates","defineComponent","h","defineComponent","useTemplates","h"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonderie/vue-courier-admin-screens",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Pre-built Vue screens for the Fonderie courier admin API — template list, template editor with revision history and rollback.",
5
5
  "keywords": [
6
6
  "fonderiejs",