@fonderie/react-courier-admin-screens 0.5.0 → 0.7.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.js CHANGED
@@ -13,10 +13,14 @@ function TemplateEditorScreen({
13
13
  client,
14
14
  type,
15
15
  locale,
16
- onSaved
16
+ onSaved,
17
+ system = false,
18
+ onDeleted,
19
+ onAddLocale
17
20
  }) {
18
21
  const { template, isLoading, error, refresh } = useTemplate(client, type, locale);
19
- const { saveTemplate } = useTemplates(client);
22
+ const { saveTemplate, removeTemplate } = useTemplates(client);
23
+ const [isDeleting, setIsDeleting] = useState(false);
20
24
  const { revisions, rollback } = useTemplateRevisions(client, type, locale);
21
25
  const [isSaving, setIsSaving] = useState(false);
22
26
  const [saveError, setSaveError] = useState(null);
@@ -86,15 +90,42 @@ function TemplateEditorScreen({
86
90
  setIsSaving(false);
87
91
  }
88
92
  };
93
+ const handleDelete = async () => {
94
+ const which = locale ? `the ${locale} version of "${type}"` : `"${type}"`;
95
+ const fallback = locale ? " People in that locale will receive the default version." : "";
96
+ if (!window.confirm(`Delete ${which}?${fallback}`)) return;
97
+ setIsDeleting(true);
98
+ setSaveError(null);
99
+ try {
100
+ await removeTemplate(type, locale);
101
+ onDeleted?.();
102
+ } catch (err) {
103
+ setSaveError(err);
104
+ } finally {
105
+ setIsDeleting(false);
106
+ }
107
+ };
89
108
  if (isLoading) return /* @__PURE__ */ jsx("p", { style: styles.status, children: "Loading template\u2026" });
90
109
  if (error)
91
110
  return /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: error.explanation });
92
111
  return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
93
- /* @__PURE__ */ jsx("h1", { style: styles.title, children: type }),
112
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
113
+ /* @__PURE__ */ jsx("h1", { style: styles.title, children: type }),
114
+ onAddLocale && !locale ? /* @__PURE__ */ jsx(
115
+ "button",
116
+ {
117
+ type: "button",
118
+ style: { ...styles.rollbackButton, marginLeft: "auto" },
119
+ onClick: () => onAddLocale(type),
120
+ children: "+ Add locale"
121
+ }
122
+ ) : null
123
+ ] }),
94
124
  /* @__PURE__ */ jsxs("p", { style: styles.meta, children: [
95
- locale ?? "base",
125
+ /* @__PURE__ */ jsx("strong", { children: locale ?? "default locale" }),
96
126
  " \xB7 v",
97
- template?.version ?? 1
127
+ template?.version ?? 1,
128
+ system ? " \xB7 built-in email: edit or roll back, it cannot be deleted" : ""
98
129
  ] }),
99
130
  /* @__PURE__ */ jsxs("div", { style: styles.split, children: [
100
131
  /* @__PURE__ */ jsxs("div", { style: styles.column, children: [
@@ -143,10 +174,49 @@ function TemplateEditorScreen({
143
174
  ),
144
175
  "Active"
145
176
  ] }),
146
- saveError && /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: saveError.explanation }),
177
+ saveError?.reason === "VERSION_CONFLICT" ? /* @__PURE__ */ jsxs("p", { style: styles.error, role: "alert", children: [
178
+ "Someone changed this template since you opened it. Reload to see their change, then edit again.",
179
+ " ",
180
+ /* @__PURE__ */ jsx(
181
+ "button",
182
+ {
183
+ type: "button",
184
+ style: styles.rollbackButton,
185
+ onClick: () => {
186
+ setSaveError(null);
187
+ void refresh();
188
+ },
189
+ children: "Reload"
190
+ }
191
+ )
192
+ ] }) : saveError ? /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: saveError.explanation }) : null,
147
193
  /* @__PURE__ */ jsxs("div", { style: styles.saveRow, children: [
148
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: isSaving || !dirty, style: dirty ? styles.button : styles.buttonDisabled, children: isSaving ? "Saving\u2026" : "Save" }),
149
- !dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: "No changes to save" })
194
+ /* @__PURE__ */ jsx(
195
+ "button",
196
+ {
197
+ type: "submit",
198
+ disabled: isSaving || !dirty,
199
+ style: dirty ? styles.button : styles.buttonDisabled,
200
+ children: isSaving ? "Saving\u2026" : "Save"
201
+ }
202
+ ),
203
+ !dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: "No changes to save" }),
204
+ !system ? /* @__PURE__ */ jsx(
205
+ "button",
206
+ {
207
+ type: "button",
208
+ disabled: isDeleting,
209
+ onClick: () => void handleDelete(),
210
+ style: {
211
+ ...styles.rollbackButton,
212
+ height: 36,
213
+ marginLeft: "auto",
214
+ color: "var(--fonderie-danger,#e00)",
215
+ borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 40%, transparent)"
216
+ },
217
+ children: isDeleting ? "Deleting\u2026" : "Delete"
218
+ }
219
+ ) : null
150
220
  ] })
151
221
  ] }),
152
222
  /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-sample", children: "Sample data" }),
@@ -212,13 +282,24 @@ function TemplateEditorScreen({
212
282
  ] });
213
283
  }
214
284
  var styles = {
215
- container: { padding: 24 },
285
+ container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
216
286
  // Two columns, always: the editor on the left, the rendered result on the
217
287
  // right. Stacking the preview under a long form hid it below the fold.
218
- split: { display: "grid", gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)", gap: 24, alignItems: "start" },
288
+ split: {
289
+ display: "grid",
290
+ gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
291
+ gap: 24,
292
+ alignItems: "start"
293
+ },
219
294
  column: { minWidth: 0, display: "flex", flexDirection: "column" },
220
295
  // Pinned while the form scrolls, so the preview stays in view as you edit.
221
- previewColumn: { minWidth: 0, display: "flex", flexDirection: "column", position: "sticky", top: 16 },
296
+ previewColumn: {
297
+ minWidth: 0,
298
+ display: "flex",
299
+ flexDirection: "column",
300
+ position: "sticky",
301
+ top: 16
302
+ },
222
303
  saveRow: { display: "flex", alignItems: "center", gap: 12, marginTop: 16 },
223
304
  previewHeader: {
224
305
  display: "flex",
@@ -228,6 +309,7 @@ var styles = {
228
309
  },
229
310
  previewSubject: { fontSize: 14, fontWeight: 600, margin: "8px 0" },
230
311
  previewFrame: {
312
+ boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))",
231
313
  width: "100%",
232
314
  height: 520,
233
315
  border: "1px solid var(--fonderie-border,#e0e0e0)",
@@ -242,60 +324,127 @@ var styles = {
242
324
  fontSize: 13,
243
325
  fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
244
326
  },
245
- title: { fontSize: 24, fontWeight: 700 },
246
- meta: { fontSize: 13, color: "var(--fonderie-text-muted,#5c5c5c)", marginBottom: 16 },
327
+ title: {
328
+ fontSize: 22,
329
+ fontWeight: 600,
330
+ margin: "8px 0 4px",
331
+ letterSpacing: "var(--fonderie-tracking-display,-0.05em)",
332
+ lineHeight: 1.25
333
+ },
334
+ meta: { fontSize: 13, color: "var(--fonderie-text-muted,#5c5c5c)", marginBottom: 20 },
247
335
  status: { padding: 24, textAlign: "center", color: "var(--fonderie-text-muted,#5c5c5c)" },
248
- error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
249
- form: { display: "flex", flexDirection: "column", gap: 4 },
250
- label: { fontSize: 13, fontWeight: 600, marginTop: 12 },
251
- input: { border: "1px solid var(--fonderie-border,#e0e0e0)", borderRadius: 8, padding: 10, fontSize: 14 },
336
+ error: {
337
+ color: "var(--fonderie-danger,#e00)",
338
+ background: "color-mix(in srgb, var(--fonderie-danger,#e00) 8%, transparent)",
339
+ borderRadius: 6,
340
+ padding: "8px 12px",
341
+ margin: "8px 0",
342
+ fontSize: 13.5
343
+ },
344
+ form: {
345
+ display: "flex",
346
+ flexDirection: "column",
347
+ gap: 4,
348
+ background: "var(--fonderie-surface,#fff)",
349
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
350
+ borderRadius: "var(--fonderie-radius-lg,8px)",
351
+ padding: "6px 20px 20px",
352
+ boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
353
+ },
354
+ label: { fontSize: 13, fontWeight: 500, marginTop: 14, marginBottom: 2 },
355
+ input: {
356
+ height: 36,
357
+ boxSizing: "border-box",
358
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
359
+ borderRadius: 6,
360
+ padding: "0 10px",
361
+ fontSize: 13.5,
362
+ fontFamily: "inherit",
363
+ background: "var(--fonderie-surface,#fff)",
364
+ color: "var(--fonderie-text,#171717)"
365
+ },
252
366
  textarea: {
253
367
  border: "1px solid var(--fonderie-border,#e0e0e0)",
254
- borderRadius: 8,
368
+ borderRadius: 6,
255
369
  padding: 10,
256
- fontSize: 14,
370
+ fontSize: 13,
371
+ lineHeight: 1.55,
372
+ background: "var(--fonderie-surface,#fff)",
373
+ color: "var(--fonderie-text,#171717)",
257
374
  fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
258
375
  },
259
376
  checkboxLabel: { display: "flex", alignItems: "center", gap: 8, marginTop: 12, fontSize: 14 },
260
377
  button: {
378
+ display: "inline-flex",
379
+ alignItems: "center",
380
+ height: 36,
381
+ boxSizing: "border-box",
261
382
  backgroundColor: "var(--fonderie-text,#171717)",
262
383
  color: "var(--fonderie-surface,#fff)",
263
- padding: "10px 20px",
264
- borderRadius: 8,
265
- border: "none",
266
- fontSize: 14,
384
+ padding: "0 16px",
385
+ borderRadius: 6,
386
+ border: "1px solid var(--fonderie-text,#171717)",
387
+ fontSize: 13.5,
267
388
  fontWeight: 600,
389
+ fontFamily: "inherit",
268
390
  cursor: "pointer",
269
391
  alignSelf: "flex-start"
270
392
  },
271
393
  buttonDisabled: {
272
- backgroundColor: "var(--fonderie-border,#e0e0e0)",
273
- color: "var(--fonderie-text-muted,#5c5c5c)",
274
- padding: "10px 20px",
275
- borderRadius: 8,
276
- border: "none",
277
- fontSize: 14,
394
+ display: "inline-flex",
395
+ alignItems: "center",
396
+ height: 36,
397
+ boxSizing: "border-box",
398
+ backgroundColor: "var(--fonderie-text,#171717)",
399
+ color: "var(--fonderie-surface,#fff)",
400
+ padding: "0 16px",
401
+ borderRadius: 6,
402
+ border: "1px solid var(--fonderie-text,#171717)",
403
+ fontSize: 13.5,
278
404
  fontWeight: 600,
279
- cursor: "not-allowed",
280
- alignSelf: "flex-start"
405
+ fontFamily: "inherit",
406
+ alignSelf: "flex-start",
407
+ opacity: 0.45,
408
+ cursor: "not-allowed"
281
409
  },
282
410
  revisions: { marginTop: 32 },
283
- subtitle: { fontSize: 16, fontWeight: 600, marginBottom: 8 },
284
- list: { listStyle: "none", padding: 0, margin: 0 },
411
+ subtitle: {
412
+ fontSize: 15,
413
+ fontWeight: 600,
414
+ margin: "0 0 10px",
415
+ letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
416
+ },
417
+ list: {
418
+ listStyle: "none",
419
+ padding: 0,
420
+ margin: 0,
421
+ background: "var(--fonderie-surface,#fff)",
422
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
423
+ borderRadius: "var(--fonderie-radius-lg,8px)",
424
+ overflow: "hidden"
425
+ },
285
426
  row: {
286
427
  display: "flex",
287
428
  justifyContent: "space-between",
288
429
  alignItems: "center",
289
- padding: "8px 0",
430
+ gap: 12,
431
+ padding: "9px 16px",
290
432
  borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)",
291
433
  fontSize: 13
292
434
  },
293
435
  rollbackButton: {
294
- background: "none",
436
+ display: "inline-flex",
437
+ alignItems: "center",
438
+ height: 28,
439
+ boxSizing: "border-box",
440
+ background: "var(--fonderie-surface,#fff)",
441
+ color: "var(--fonderie-text,#171717)",
295
442
  border: "1px solid var(--fonderie-border,#e0e0e0)",
296
- borderRadius: 8,
297
- padding: "4px 10px",
298
- fontSize: 12,
443
+ borderRadius: 6,
444
+ padding: "0 10px",
445
+ fontSize: 12.5,
446
+ fontWeight: 500,
447
+ fontFamily: "inherit",
299
448
  cursor: "pointer"
300
449
  }
301
450
  };
@@ -303,14 +452,37 @@ var styles = {
303
452
  // src/screens/TemplateListScreen.tsx
304
453
  import { useTemplates as useTemplates2 } from "@fonderie/react-courier-admin";
305
454
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
306
- function TemplateListScreen({ client, onSelectTemplate }) {
455
+ function TemplateListScreen({
456
+ client,
457
+ onSelectTemplate,
458
+ onCreateTemplate
459
+ }) {
307
460
  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
461
  return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
312
- /* @__PURE__ */ jsx2("h1", { style: styles2.title, children: "Templates" }),
313
- /* @__PURE__ */ jsx2("ul", { style: styles2.list, children: templates.map((template) => /* @__PURE__ */ jsx2("li", { style: styles2.row, children: /* @__PURE__ */ jsxs2(
462
+ /* @__PURE__ */ jsxs2(
463
+ "div",
464
+ {
465
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 },
466
+ children: [
467
+ /* @__PURE__ */ jsx2("h1", { style: styles2.title, children: "Templates" }),
468
+ onCreateTemplate ? /* @__PURE__ */ jsx2(
469
+ "button",
470
+ {
471
+ type: "button",
472
+ onClick: () => onCreateTemplate({
473
+ locales: [
474
+ ...new Set(templates.map((t) => t.locale).filter((l) => !!l))
475
+ ]
476
+ }),
477
+ style: styles2.newButton,
478
+ children: "New template"
479
+ }
480
+ ) : null
481
+ ]
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsx2("p", { style: styles2.hint, children: "Every email the app sends. Open one to edit its copy and preview it live." }),
485
+ 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
486
  "button",
315
487
  {
316
488
  type: "button",
@@ -318,37 +490,315 @@ function TemplateListScreen({ client, onSelectTemplate }) {
318
490
  style: styles2.rowButton,
319
491
  children: [
320
492
  /* @__PURE__ */ jsx2("span", { style: styles2.type, children: template.type }),
321
- /* @__PURE__ */ jsx2("span", { style: styles2.locale, children: template.locale ?? "base" }),
322
- /* @__PURE__ */ jsx2("span", { style: template.active ? styles2.active : styles2.inactive, children: template.active ? "Active" : "Inactive" })
493
+ /* @__PURE__ */ jsx2("span", { style: styles2.locale, children: template.locale ?? "default locale" }),
494
+ template.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: "built-in" }) : null,
495
+ /* @__PURE__ */ jsxs2("span", { style: template.active ? styles2.active : styles2.inactive, children: [
496
+ /* @__PURE__ */ jsx2("span", { style: styles2.dot }),
497
+ template.active ? "active" : "inactive"
498
+ ] })
323
499
  ]
324
500
  }
325
501
  ) }, `${template.type}:${template.locale ?? "base"}`)) })
326
502
  ] });
327
503
  }
504
+ var pill = {
505
+ display: "inline-flex",
506
+ alignItems: "center",
507
+ gap: 6,
508
+ borderRadius: 999,
509
+ padding: "2px 9px 2px 8px",
510
+ fontSize: 12,
511
+ fontWeight: 600,
512
+ lineHeight: 1.5,
513
+ minWidth: 64
514
+ };
328
515
  var styles2 = {
329
- container: { padding: 24, maxWidth: 640 },
330
- title: { fontSize: 24, fontWeight: 700, marginBottom: 16 },
331
- status: { padding: 24, textAlign: "center", color: "var(--fonderie-text-muted,#5c5c5c)" },
516
+ container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
517
+ title: {
518
+ fontSize: 22,
519
+ fontWeight: 600,
520
+ margin: 0,
521
+ letterSpacing: "var(--fonderie-tracking-display,-0.05em)",
522
+ lineHeight: 1.25
523
+ },
524
+ hint: { fontSize: 13.5, color: "var(--fonderie-text-muted,#5c5c5c)", margin: "4px 0 24px" },
525
+ status: { padding: "24px 0", color: "var(--fonderie-text-muted,#5c5c5c)", fontSize: 13.5 },
332
526
  error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
333
- list: { listStyle: "none", padding: 0, margin: 0 },
527
+ list: {
528
+ listStyle: "none",
529
+ padding: 0,
530
+ margin: 0,
531
+ background: "var(--fonderie-surface,#fff)",
532
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
533
+ borderRadius: "var(--fonderie-radius-lg,8px)",
534
+ overflow: "hidden",
535
+ boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
536
+ },
334
537
  row: { borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)" },
335
538
  rowButton: {
336
539
  width: "100%",
337
540
  display: "flex",
338
541
  justifyContent: "space-between",
339
542
  alignItems: "center",
340
- padding: "12px 0",
543
+ gap: 16,
544
+ padding: "13px 16px",
341
545
  background: "none",
342
546
  border: "none",
343
547
  cursor: "pointer",
344
- textAlign: "left"
548
+ textAlign: "left",
549
+ color: "var(--fonderie-text,#171717)",
550
+ fontFamily: "inherit"
551
+ },
552
+ type: {
553
+ fontSize: 13,
554
+ fontWeight: 600,
555
+ flex: 1,
556
+ fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
557
+ },
558
+ locale: { fontSize: 12.5, color: "var(--fonderie-text-muted,#5c5c5c)" },
559
+ dot: { width: 6, height: 6, borderRadius: 999, background: "currentColor" },
560
+ builtIn: {
561
+ fontSize: 11,
562
+ fontWeight: 500,
563
+ padding: "1px 7px",
564
+ borderRadius: 999,
565
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
566
+ color: "var(--fonderie-text-muted,#5c5c5c)"
567
+ },
568
+ newButton: {
569
+ display: "inline-flex",
570
+ alignItems: "center",
571
+ height: 32,
572
+ boxSizing: "border-box",
573
+ borderRadius: 6,
574
+ padding: "0 12px",
575
+ fontSize: 13,
576
+ fontWeight: 500,
577
+ fontFamily: "inherit",
578
+ cursor: "pointer",
579
+ backgroundColor: "var(--fonderie-text,#171717)",
580
+ color: "var(--fonderie-surface,#fff)",
581
+ border: "1px solid var(--fonderie-text,#171717)"
345
582
  },
346
- type: { fontSize: 14, fontWeight: 600, flex: 1 },
347
- locale: { fontSize: 13, color: "var(--fonderie-text-muted,#5c5c5c)", marginRight: 16 },
348
- active: { fontSize: 12, color: "var(--fonderie-accent-strong,#009767)" },
349
- inactive: { fontSize: 12, color: "var(--fonderie-text-muted,#5c5c5c)" }
583
+ active: {
584
+ ...pill,
585
+ color: "var(--fonderie-accent-strong,#009767)",
586
+ background: "color-mix(in srgb, var(--fonderie-accent-strong,#009767) 13%, transparent)"
587
+ },
588
+ inactive: {
589
+ ...pill,
590
+ color: "var(--fonderie-text-muted,#5c5c5c)",
591
+ background: "color-mix(in srgb, var(--fonderie-text-muted,#5c5c5c) 10%, transparent)"
592
+ }
593
+ };
594
+
595
+ // src/screens/TemplateCreateScreen.tsx
596
+ import { FonderieApiError } from "@fonderie/client";
597
+ import { useEffect as useEffect2, useState as useState2 } from "react";
598
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
599
+ var TYPE_RE = /^[a-z0-9][a-z0-9_-]{0,80}$/;
600
+ var LOCALE_RE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
601
+ function TemplateCreateScreen({
602
+ client,
603
+ type,
604
+ locales = [],
605
+ onCreated
606
+ }) {
607
+ const addingLocale = type !== void 0;
608
+ const [newType, setNewType] = useState2("");
609
+ const [locale, setLocale] = useState2("");
610
+ const [subject, setSubject] = useState2("");
611
+ const [html, setHtml] = useState2("");
612
+ const [text, setText] = useState2("");
613
+ const [error, setError] = useState2(null);
614
+ const [busy, setBusy] = useState2(false);
615
+ useEffect2(() => {
616
+ if (!type) return;
617
+ client.getTemplate(type, null).then(({ result }) => {
618
+ setSubject(result.subject ?? "");
619
+ setHtml(result.html ?? "");
620
+ setText(result.text);
621
+ }).catch(() => void 0);
622
+ }, [client, type]);
623
+ const submit = async (e) => {
624
+ e.preventDefault();
625
+ setError(null);
626
+ const t = addingLocale ? type : newType.trim();
627
+ const l = locale.trim() || null;
628
+ if (!TYPE_RE.test(t))
629
+ return setError("Type: lowercase letters, digits, - and _ (e.g. weekly-digest).");
630
+ if (addingLocale && !l) return setError("Choose the locale to add, e.g. fr or fr-CA.");
631
+ if (l && !LOCALE_RE.test(l)) return setError("Locale: a language tag such as fr, es or fr-CA.");
632
+ if (!text.trim())
633
+ return setError("The plain-text body is required \u2014 it is what every email client can show.");
634
+ setBusy(true);
635
+ try {
636
+ const exists = await client.getTemplate(t, l).then(
637
+ () => true,
638
+ (err) => !(err instanceof FonderieApiError && err.status === 404)
639
+ );
640
+ if (exists) {
641
+ setError(`"${t}"${l ? ` (${l})` : ""} already exists \u2014 open it from the list to edit.`);
642
+ return;
643
+ }
644
+ const input = { text, active: true };
645
+ if (subject) input.subject = subject;
646
+ if (html) input.html = html;
647
+ await client.setTemplate(t, input, l);
648
+ onCreated?.({ type: t, locale: l });
649
+ } catch (err) {
650
+ setError(err instanceof FonderieApiError ? err.explanation : String(err));
651
+ } finally {
652
+ setBusy(false);
653
+ }
654
+ };
655
+ return /* @__PURE__ */ jsxs3("div", { style: styles3.container, children: [
656
+ /* @__PURE__ */ jsx3("h1", { style: styles3.title, children: addingLocale ? `Add a locale to ${type}` : "New template" }),
657
+ /* @__PURE__ */ jsx3("p", { style: styles3.meta, children: addingLocale ? "Starts from the default copy. People whose locale matches receive this version; everyone else keeps the default." : "For an email your app sends that has no stored copy yet. The type must match what the app sends." }),
658
+ /* @__PURE__ */ jsxs3("form", { style: styles3.form, onSubmit: (e) => void submit(e), children: [
659
+ !addingLocale ? /* @__PURE__ */ jsxs3(Fragment, { children: [
660
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: "Type" }),
661
+ /* @__PURE__ */ jsx3(
662
+ "input",
663
+ {
664
+ id: "new-template-type",
665
+ style: styles3.input,
666
+ value: newType,
667
+ placeholder: "weekly-digest",
668
+ autoComplete: "off",
669
+ spellCheck: false,
670
+ onChange: (e) => setNewType(e.target.value)
671
+ }
672
+ )
673
+ ] }) : null,
674
+ /* @__PURE__ */ jsxs3("label", { style: styles3.label, htmlFor: "new-template-locale", children: [
675
+ "Locale ",
676
+ addingLocale ? "" : "(optional \u2014 empty is the default)"
677
+ ] }),
678
+ /* @__PURE__ */ jsx3(
679
+ "input",
680
+ {
681
+ id: "new-template-locale",
682
+ style: styles3.input,
683
+ value: locale,
684
+ placeholder: "fr",
685
+ list: "new-template-locales",
686
+ autoComplete: "off",
687
+ spellCheck: false,
688
+ onChange: (e) => setLocale(e.target.value)
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsx3("datalist", { id: "new-template-locales", children: [...new Set(locales)].map((l) => /* @__PURE__ */ jsx3("option", { value: l }, l)) }),
692
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-subject", children: "Subject" }),
693
+ /* @__PURE__ */ jsx3(
694
+ "input",
695
+ {
696
+ id: "new-template-subject",
697
+ style: styles3.input,
698
+ value: subject,
699
+ onChange: (e) => setSubject(e.target.value)
700
+ }
701
+ ),
702
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-html", children: "HTML body" }),
703
+ /* @__PURE__ */ jsx3(
704
+ "textarea",
705
+ {
706
+ id: "new-template-html",
707
+ style: { ...styles3.textarea, minHeight: 160 },
708
+ value: html,
709
+ onChange: (e) => setHtml(e.target.value)
710
+ }
711
+ ),
712
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-text", children: "Plain-text body" }),
713
+ /* @__PURE__ */ jsx3(
714
+ "textarea",
715
+ {
716
+ id: "new-template-text",
717
+ style: { ...styles3.textarea, minHeight: 100 },
718
+ value: text,
719
+ onChange: (e) => setText(e.target.value)
720
+ }
721
+ ),
722
+ error ? /* @__PURE__ */ jsx3("p", { style: styles3.error, role: "alert", children: error }) : null,
723
+ /* @__PURE__ */ jsx3("button", { type: "submit", disabled: busy, style: styles3.button, children: busy ? "Creating\u2026" : addingLocale ? "Add locale" : "Create template" })
724
+ ] })
725
+ ] });
726
+ }
727
+ var MONO = 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)';
728
+ var styles3 = {
729
+ container: { padding: "8px 40px 64px", maxWidth: 760, boxSizing: "border-box" },
730
+ title: {
731
+ fontSize: 22,
732
+ fontWeight: 600,
733
+ margin: "8px 0 4px",
734
+ letterSpacing: "var(--fonderie-tracking-display,-0.05em)",
735
+ lineHeight: 1.25
736
+ },
737
+ meta: {
738
+ fontSize: 13,
739
+ color: "var(--fonderie-text-muted,#5c5c5c)",
740
+ marginBottom: 20,
741
+ lineHeight: 1.5
742
+ },
743
+ form: {
744
+ display: "flex",
745
+ flexDirection: "column",
746
+ gap: 4,
747
+ background: "var(--fonderie-surface,#fff)",
748
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
749
+ borderRadius: "var(--fonderie-radius-lg,8px)",
750
+ padding: "6px 20px 20px"
751
+ },
752
+ label: { fontSize: 13, fontWeight: 500, marginTop: 14, marginBottom: 2 },
753
+ input: {
754
+ height: 36,
755
+ boxSizing: "border-box",
756
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
757
+ borderRadius: 6,
758
+ padding: "0 10px",
759
+ fontSize: 13.5,
760
+ fontFamily: "inherit",
761
+ background: "var(--fonderie-surface,#fff)",
762
+ color: "var(--fonderie-text,#171717)"
763
+ },
764
+ textarea: {
765
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
766
+ borderRadius: 6,
767
+ padding: 10,
768
+ fontSize: 13,
769
+ lineHeight: 1.55,
770
+ background: "var(--fonderie-surface,#fff)",
771
+ color: "var(--fonderie-text,#171717)",
772
+ fontFamily: MONO
773
+ },
774
+ error: {
775
+ color: "var(--fonderie-danger,#e00)",
776
+ background: "color-mix(in srgb, var(--fonderie-danger,#e00) 8%, transparent)",
777
+ borderRadius: 6,
778
+ padding: "8px 12px",
779
+ margin: "8px 0",
780
+ fontSize: 13.5
781
+ },
782
+ button: {
783
+ display: "inline-flex",
784
+ alignItems: "center",
785
+ height: 36,
786
+ boxSizing: "border-box",
787
+ backgroundColor: "var(--fonderie-text,#171717)",
788
+ color: "var(--fonderie-surface,#fff)",
789
+ padding: "0 16px",
790
+ borderRadius: 6,
791
+ border: "1px solid var(--fonderie-text,#171717)",
792
+ fontSize: 13.5,
793
+ fontWeight: 600,
794
+ fontFamily: "inherit",
795
+ cursor: "pointer",
796
+ alignSelf: "flex-start",
797
+ marginTop: 16
798
+ }
350
799
  };
351
800
  export {
801
+ TemplateCreateScreen,
352
802
  TemplateEditorScreen,
353
803
  TemplateListScreen
354
804
  };