@fonderie/react-courier-admin-screens 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,12 +2,15 @@
2
2
  import {
3
3
  createAdminT,
4
4
  formatAdminDate,
5
- groupTemplatesByType,
6
- missingTemplateLocales
5
+ suggestTemplateLocales,
6
+ templateLanguages
7
7
  } from "@fonderie/client";
8
8
  import {
9
+ useBuiltInTemplate,
9
10
  useTemplate,
11
+ useTemplateCatalog,
10
12
  useTemplatePreview,
13
+ useTemplateResolution,
11
14
  useTemplateRevisions,
12
15
  useTemplates
13
16
  } from "@fonderie/react-courier-admin";
@@ -28,8 +31,16 @@ function TemplateEditorScreen({
28
31
  }) {
29
32
  const t = useMemo(() => createAdminT(uiLocale), [uiLocale]);
30
33
  const { template, isLoading, error, refresh } = useTemplate(client, type, locale);
31
- const { templates, saveTemplate, removeTemplate } = useTemplates(client);
32
- const siblings = groupTemplatesByType(templates).find((g) => g.type === type)?.entries ?? [];
34
+ const notSaved = error?.status === 404;
35
+ const { builtIn, isLoading: builtInLoading } = useBuiltInTemplate(client, type, locale);
36
+ const { catalog, refresh: refreshCatalog } = useTemplateCatalog(client);
37
+ const defaultLocale = catalog?.defaultLocale ?? "en-US";
38
+ const email = catalog?.emails.find((e) => e.type === type);
39
+ const languages = email ? templateLanguages(email, defaultLocale) : [];
40
+ const source = template ?? (notSaved ? builtIn : null);
41
+ const { resolution, resolve } = useTemplateResolution(client);
42
+ const [probe, setProbe] = useState("");
43
+ const { saveTemplate, removeTemplate } = useTemplates(client);
33
44
  const [isDeleting, setIsDeleting] = useState(false);
34
45
  const { revisions, rollback } = useTemplateRevisions(client, type, locale);
35
46
  const [isSaving, setIsSaving] = useState(false);
@@ -42,12 +53,15 @@ function TemplateEditorScreen({
42
53
  const [sampleJson, setSampleJson] = useState("{}");
43
54
  const [sampleError, setSampleError] = useState(null);
44
55
  useEffect(() => {
45
- if (!template) return;
46
- setSubject(template.subject ?? "");
47
- setHtml(template.html ?? "");
48
- setText(template.text);
49
- setActive(template.active);
50
- }, [template]);
56
+ if (!source) return;
57
+ setSubject(source.subject ?? "");
58
+ setHtml(source.html ?? "");
59
+ setText(source.text);
60
+ setActive(template ? template.active : true);
61
+ }, [source, template]);
62
+ useEffect(() => {
63
+ void resolve(type, locale ?? defaultLocale);
64
+ }, [resolve, type, locale, defaultLocale]);
51
65
  const run = useCallback(async () => {
52
66
  let data = {};
53
67
  try {
@@ -75,13 +89,15 @@ function TemplateEditorScreen({
75
89
  }, [renderPreview, type, text, subject, html, locale, sampleJson, t]);
76
90
  const runRef = useRef(run);
77
91
  runRef.current = run;
78
- const loadedType = template ? `${type}:${locale ?? ""}` : null;
92
+ const loadedType = source ? `${type}:${locale ?? ""}` : null;
79
93
  useEffect(() => {
80
94
  if (!loadedType) return;
81
95
  const timer = setTimeout(() => void runRef.current(), PREVIEW_DEBOUNCE_MS);
82
96
  return () => clearTimeout(timer);
83
97
  }, [loadedType, subject, html, text, sampleJson]);
84
- const dirty = !!template && (subject !== (template.subject ?? "") || html !== (template.html ?? "") || text !== template.text || active !== template.active);
98
+ const dirty = notSaved && !!builtIn || !!template && (subject !== (template.subject ?? "") || html !== (template.html ?? "") || text !== template.text || active !== template.active);
99
+ const protectedRow = !locale && (email?.system ?? system);
100
+ const shipsHere = locale ? languages.some((l) => l.locale === locale && l.builtIn) : !!email?.builtIn.default;
85
101
  const handleSubmit = async (event) => {
86
102
  event.preventDefault();
87
103
  setIsSaving(true);
@@ -92,7 +108,7 @@ function TemplateEditorScreen({
92
108
  if (html) input.html = html;
93
109
  if (template?.version !== void 0) input.ifVersion = template.version;
94
110
  await saveTemplate(type, input, locale);
95
- await refresh();
111
+ await Promise.all([refresh(), refreshCatalog()]);
96
112
  onSaved?.();
97
113
  } catch (err) {
98
114
  setSaveError(err);
@@ -100,9 +116,32 @@ function TemplateEditorScreen({
100
116
  setIsSaving(false);
101
117
  }
102
118
  };
103
- const switchTo = (entry) => {
104
- if (dirty && !window.confirm(t("templates.editor.discardChanges"))) return;
105
- onSelectLocale?.(entry);
119
+ const switchTo = (next) => {
120
+ if (dirty && !(notSaved && !!builtIn) && !window.confirm(t("templates.editor.discardChanges")))
121
+ return;
122
+ onSelectLocale?.(next);
123
+ };
124
+ const handleReset = async () => {
125
+ if (!builtIn || !window.confirm(t("templates.editor.confirmReset", { locale: locale ?? defaultLocale })))
126
+ return;
127
+ setIsDeleting(true);
128
+ setSaveError(null);
129
+ try {
130
+ if (locale) {
131
+ await removeTemplate(type, locale);
132
+ } else {
133
+ const input = { text: builtIn.text, active };
134
+ if (builtIn.subject) input.subject = builtIn.subject;
135
+ if (builtIn.html) input.html = builtIn.html;
136
+ if (template?.version !== void 0) input.ifVersion = template.version;
137
+ await saveTemplate(type, input, null);
138
+ }
139
+ await Promise.all([refresh(), refreshCatalog()]);
140
+ } catch (err) {
141
+ setSaveError(err);
142
+ } finally {
143
+ setIsDeleting(false);
144
+ }
106
145
  };
107
146
  const handleDelete = async () => {
108
147
  const message = locale ? t("templates.editor.confirmDeleteLocale", { type, locale }) : t("templates.editor.confirmDelete", { type });
@@ -118,8 +157,9 @@ function TemplateEditorScreen({
118
157
  setIsDeleting(false);
119
158
  }
120
159
  };
121
- if (isLoading) return /* @__PURE__ */ jsx("p", { style: styles.status, children: t("templates.editor.loading") });
122
- if (error)
160
+ if (isLoading || notSaved && builtInLoading)
161
+ return /* @__PURE__ */ jsx("p", { style: styles.status, children: t("templates.editor.loading") });
162
+ if (error && !(notSaved && builtIn))
123
163
  return /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: error.explanation });
124
164
  return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
125
165
  /* @__PURE__ */ jsx("h1", { style: styles.title, children: type }),
@@ -127,19 +167,23 @@ function TemplateEditorScreen({
127
167
  onSelectLocale ? (
128
168
  // Each tab opens another stored row, so this is navigation, not an
129
169
  // ARIA tablist: plain buttons, the open one marked aria-current.
130
- /* @__PURE__ */ jsx("nav", { "aria-label": t("templates.editor.locales"), style: styles.tabs, children: siblings.map((entry) => {
131
- const selected = entry.locale === (locale ?? null);
170
+ /* @__PURE__ */ jsx("nav", { "aria-label": t("templates.editor.locales"), style: styles.tabs, children: languages.map((l) => {
171
+ const selected = l.locale === (locale ?? null);
132
172
  return /* @__PURE__ */ jsx(
133
173
  "button",
134
174
  {
135
175
  type: "button",
136
176
  "aria-current": selected ? "true" : void 0,
137
- onClick: () => selected ? void 0 : switchTo(entry),
138
- style: selected ? styles.tabSelected : entry.active ? styles.tab : styles.tabInactive,
139
- title: entry.active ? void 0 : t("common.status.inactive"),
140
- children: entry.locale ?? t("templates.defaultChip")
177
+ onClick: () => selected ? void 0 : switchTo({ type, locale: l.locale, system: !!email?.system }),
178
+ style: {
179
+ ...selected ? styles.tabSelected : styles.tab,
180
+ ...l.saved ? {} : styles.tabBuiltIn,
181
+ ...l.active ? {} : styles.tabInactive
182
+ },
183
+ title: l.active ? void 0 : t("common.status.inactive"),
184
+ children: l.label
141
185
  },
142
- entry.locale ?? ""
186
+ l.label
143
187
  );
144
188
  }) })
145
189
  ) : null,
@@ -150,17 +194,21 @@ function TemplateEditorScreen({
150
194
  style: { ...styles.rollbackButton, marginLeft: "auto" },
151
195
  onClick: () => {
152
196
  if (dirty && !window.confirm(t("templates.editor.discardChanges"))) return;
153
- onAddLocale(type, { locales: missingTemplateLocales(templates, type) });
197
+ onAddLocale(type, {
198
+ locales: catalog ? suggestTemplateLocales(catalog, type) : [],
199
+ defaultLocale
200
+ });
154
201
  },
155
202
  children: t("templates.editor.addLocale")
156
203
  }
157
204
  ) : null
158
205
  ] }) : null,
159
206
  /* @__PURE__ */ jsxs("p", { style: styles.meta, children: [
160
- /* @__PURE__ */ jsx("strong", { children: locale ?? t("templates.defaultLocale") }),
161
- " \xB7 v",
162
- template?.version ?? 1,
163
- system ? ` \xB7 ${t("templates.editor.builtInNote")}` : ""
207
+ /* @__PURE__ */ jsx("strong", { children: locale ?? defaultLocale }),
208
+ template ? ` \xB7 v${template.version}` : "",
209
+ template && protectedRow ? ` \xB7 ${t("templates.editor.builtInNote")}` : "",
210
+ !template && builtIn ? ` \xB7 ${t("templates.editor.builtInCopy")}` : "",
211
+ resolution && resolution.chain.length > 1 && resolution.requested === (locale ?? defaultLocale) ? ` \xB7 ${t("templates.editor.chain", { chain: [...resolution.chain, defaultLocale].join(" \u2192 ") })}` : ""
164
212
  ] }),
165
213
  /* @__PURE__ */ jsxs("div", { style: styles.split, children: [
166
214
  /* @__PURE__ */ jsxs("div", { style: styles.column, children: [
@@ -236,7 +284,16 @@ function TemplateEditorScreen({
236
284
  }
237
285
  ),
238
286
  !dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: t("templates.editor.noChanges") }),
239
- !system ? /* @__PURE__ */ jsx(
287
+ template && shipsHere && builtIn ? /* @__PURE__ */ jsx(
288
+ "button",
289
+ {
290
+ type: "button",
291
+ disabled: isDeleting,
292
+ onClick: () => void handleReset(),
293
+ style: { ...styles.rollbackButton, height: 36, marginLeft: "auto" },
294
+ children: t("templates.editor.resetToBuiltIn")
295
+ }
296
+ ) : template && !protectedRow ? /* @__PURE__ */ jsx(
240
297
  "button",
241
298
  {
242
299
  type: "button",
@@ -291,6 +348,43 @@ function TemplateEditorScreen({
291
348
  ) : preview ? /* @__PURE__ */ jsx("pre", { style: styles.previewText, children: preview.text }) : /* @__PURE__ */ jsx("p", { style: styles.meta, children: t("templates.editor.nothingRendered") })
292
349
  ] })
293
350
  ] }),
351
+ /* @__PURE__ */ jsxs("div", { style: styles.revisions, children: [
352
+ /* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: t("templates.editor.whoReceives") }),
353
+ /* @__PURE__ */ jsxs(
354
+ "form",
355
+ {
356
+ style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" },
357
+ onSubmit: (event) => {
358
+ event.preventDefault();
359
+ if (probe.trim()) void resolve(type, probe.trim());
360
+ },
361
+ children: [
362
+ /* @__PURE__ */ jsx(
363
+ "input",
364
+ {
365
+ "aria-label": t("templates.editor.whoReceives"),
366
+ style: { ...styles.input, width: 200 },
367
+ value: probe,
368
+ placeholder: t("templates.editor.whoPlaceholder"),
369
+ spellCheck: false,
370
+ onChange: (event) => setProbe(event.target.value)
371
+ }
372
+ ),
373
+ /* @__PURE__ */ jsx("button", { type: "submit", style: { ...styles.rollbackButton, height: 36 }, children: t("templates.editor.check") }),
374
+ resolution && probe.trim() ? /* @__PURE__ */ jsxs("span", { style: { ...styles.meta, marginBottom: 0 }, role: "status", children: [
375
+ t("templates.editor.receives", {
376
+ requested: resolution.requested,
377
+ sent: resolution.sent
378
+ }),
379
+ " ",
380
+ resolution.source === "saved" ? t("templates.editor.sourceSaved") : t("templates.editor.sourceBuiltIn"),
381
+ " ",
382
+ resolution.chain.length > 0 ? `(${[...resolution.chain, resolution.defaultLocale].join(" \u2192 ")})` : ""
383
+ ] }) : null
384
+ ]
385
+ }
386
+ )
387
+ ] }),
294
388
  revisions.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles.revisions, children: [
295
389
  /* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: t("templates.editor.history") }),
296
390
  /* @__PURE__ */ jsx("ul", { style: styles.list, children: revisions.map((rev) => /* @__PURE__ */ jsxs("li", { style: styles.row, children: [
@@ -347,7 +441,9 @@ var styles = {
347
441
  borderBottomColor: "var(--fonderie-text,#171717)",
348
442
  cursor: "default"
349
443
  },
350
- tabInactive: { ...tab, opacity: 0.55, textDecoration: "line-through" },
444
+ tabInactive: { opacity: 0.55, textDecoration: "line-through" },
445
+ // Fonderie's built-in copy, not saved by the app — the list's dashed chip.
446
+ tabBuiltIn: { fontStyle: "italic" },
351
447
  // Two columns, always: the editor on the left, the rendered result on the
352
448
  // right. Stacking the preview under a long form hid it below the fold.
353
449
  split: {
@@ -517,10 +613,10 @@ var styles = {
517
613
  // src/screens/TemplateListScreen.tsx
518
614
  import {
519
615
  createAdminT as createAdminT2,
520
- groupTemplatesByType as groupTemplatesByType2
616
+ templateLanguages as templateLanguages2
521
617
  } from "@fonderie/client";
522
- import { useTemplates as useTemplates2 } from "@fonderie/react-courier-admin";
523
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
618
+ import { useTemplateCatalog as useTemplateCatalog2 } from "@fonderie/react-courier-admin";
619
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
524
620
  function TemplateListScreen({
525
621
  client,
526
622
  onSelectTemplate,
@@ -528,8 +624,14 @@ function TemplateListScreen({
528
624
  locale
529
625
  }) {
530
626
  const t = createAdminT2(locale);
531
- const { templates, isLoading, error } = useTemplates2(client);
532
- const groups = groupTemplatesByType2(templates);
627
+ const { catalog, isLoading, error } = useTemplateCatalog2(client);
628
+ const emails = catalog?.emails ?? [];
629
+ const defaultLocale = catalog?.defaultLocale ?? "en-US";
630
+ const chipStyle = (l) => ({
631
+ ...chip,
632
+ ...l.saved ? {} : chipBuiltIn,
633
+ ...l.active ? {} : chipOff
634
+ });
533
635
  return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
534
636
  /* @__PURE__ */ jsxs2(
535
637
  "div",
@@ -543,8 +645,12 @@ function TemplateListScreen({
543
645
  type: "button",
544
646
  onClick: () => onCreateTemplate({
545
647
  locales: [
546
- ...new Set(templates.map((row) => row.locale).filter((l) => !!l))
547
- ]
648
+ ...new Set(
649
+ emails.flatMap(
650
+ (e) => templateLanguages2(e, defaultLocale).filter((l) => l.locale !== null).map((l) => l.label)
651
+ )
652
+ )
653
+ ].sort()
548
654
  }),
549
655
  style: styles2.newButton,
550
656
  children: t("templates.list.newTemplate")
@@ -554,39 +660,47 @@ function TemplateListScreen({
554
660
  }
555
661
  ),
556
662
  /* @__PURE__ */ jsx2("p", { style: styles2.hint, children: t("templates.list.hint") }),
557
- isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: t("templates.list.loading") }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsx2("ul", { style: styles2.list, children: groups.map((group) => /* @__PURE__ */ jsxs2("li", { style: styles2.row, children: [
558
- /* @__PURE__ */ jsxs2(
559
- "button",
560
- {
561
- type: "button",
562
- onClick: () => onSelectTemplate?.(group.primary),
563
- style: styles2.rowButton,
564
- children: [
565
- /* @__PURE__ */ jsx2("span", { style: styles2.type, children: group.type }),
566
- group.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: t("templates.builtInBadge") }) : null,
567
- /* @__PURE__ */ jsxs2("span", { style: group.primary.active ? styles2.active : styles2.inactive, children: [
663
+ isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: t("templates.list.loading") }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
664
+ /* @__PURE__ */ jsx2("ul", { style: styles2.list, children: emails.map((email) => {
665
+ const languages = templateLanguages2(email, defaultLocale);
666
+ const primary = languages.find((l) => l.locale === null) ?? languages[0];
667
+ const open = (l) => onSelectTemplate?.({
668
+ type: email.type,
669
+ locale: l?.locale ?? null,
670
+ system: email.system
671
+ });
672
+ return (
673
+ // Columns, not a flex row: the language chips line up down the
674
+ // list whatever the email name or badge.
675
+ /* @__PURE__ */ jsxs2("li", { style: styles2.row, children: [
676
+ /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => open(primary), style: styles2.rowButton, children: [
677
+ /* @__PURE__ */ jsx2("span", { style: styles2.type, children: email.type }),
678
+ email.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: t("templates.builtInBadge") }) : /* @__PURE__ */ jsx2("span", {})
679
+ ] }),
680
+ /* @__PURE__ */ jsx2("span", { style: styles2.chips, children: languages.map((l) => /* @__PURE__ */ jsx2(
681
+ "button",
682
+ {
683
+ type: "button",
684
+ onClick: () => open(l),
685
+ style: chipStyle(l),
686
+ "aria-label": t("templates.list.openLocale", {
687
+ type: email.type,
688
+ locale: l.label
689
+ }),
690
+ title: l.active ? void 0 : t("common.status.inactive"),
691
+ children: l.label
692
+ },
693
+ l.label
694
+ )) }),
695
+ /* @__PURE__ */ jsxs2("span", { style: primary?.active === false ? styles2.inactive : styles2.active, children: [
568
696
  /* @__PURE__ */ jsx2("span", { style: styles2.dot }),
569
- group.primary.active ? t("common.status.active") : t("common.status.inactive")
697
+ primary?.active === false ? t("common.status.inactive") : t("common.status.active")
570
698
  ] })
571
- ]
572
- }
573
- ),
574
- /* @__PURE__ */ jsx2("span", { style: styles2.chips, children: group.entries.map((entry) => {
575
- const name = entry.locale ?? t("templates.defaultChip");
576
- return /* @__PURE__ */ jsx2(
577
- "button",
578
- {
579
- type: "button",
580
- onClick: () => onSelectTemplate?.(entry),
581
- style: entry.active ? styles2.chip : styles2.chipInactive,
582
- "aria-label": t("templates.list.openLocale", { type: group.type, locale: name }),
583
- title: entry.active ? void 0 : t("common.status.inactive"),
584
- children: name
585
- },
586
- entry.locale ?? ""
699
+ ] }, email.type)
587
700
  );
588
- }) })
589
- ] }, group.type)) })
701
+ }) }),
702
+ /* @__PURE__ */ jsx2("p", { style: styles2.legend, children: t("templates.list.legend") })
703
+ ] })
590
704
  ] });
591
705
  }
592
706
  var pill = {
@@ -600,6 +714,7 @@ var pill = {
600
714
  lineHeight: 1.5,
601
715
  minWidth: 64
602
716
  };
717
+ var MONO = 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)';
603
718
  var chip = {
604
719
  height: 24,
605
720
  boxSizing: "border-box",
@@ -609,9 +724,15 @@ var chip = {
609
724
  background: "var(--fonderie-surface,#fff)",
610
725
  color: "var(--fonderie-text,#171717)",
611
726
  fontSize: 12,
612
- fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)',
727
+ fontFamily: MONO,
613
728
  cursor: "pointer"
614
729
  };
730
+ var chipBuiltIn = {
731
+ borderStyle: "dashed",
732
+ background: "transparent",
733
+ color: "var(--fonderie-text-muted,#5c5c5c)"
734
+ };
735
+ var chipOff = { opacity: 0.5, textDecoration: "line-through" };
615
736
  var styles2 = {
616
737
  container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
617
738
  title: {
@@ -622,6 +743,7 @@ var styles2 = {
622
743
  lineHeight: 1.25
623
744
  },
624
745
  hint: { fontSize: 13.5, color: "var(--fonderie-text-muted,#5c5c5c)", margin: "4px 0 24px" },
746
+ legend: { fontSize: 12.5, color: "var(--fonderie-text-muted,#5c5c5c)", margin: "10px 2px 0" },
625
747
  status: { padding: "24px 0", color: "var(--fonderie-text-muted,#5c5c5c)", fontSize: 13.5 },
626
748
  error: { color: "var(--fonderie-danger,#e00)", marginBottom: 12, fontSize: 14 },
627
749
  list: {
@@ -634,20 +756,20 @@ var styles2 = {
634
756
  overflow: "hidden",
635
757
  boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
636
758
  },
759
+ // name + badge | languages | status — fixed tracks so every column aligns.
637
760
  row: {
638
- display: "flex",
761
+ display: "grid",
762
+ gridTemplateColumns: "minmax(0, 1fr) minmax(180px, 280px) 88px",
639
763
  alignItems: "center",
640
- gap: 8,
764
+ gap: 16,
641
765
  paddingRight: 16,
642
766
  borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)"
643
767
  },
644
768
  rowButton: {
645
- flex: 1,
646
769
  minWidth: 0,
647
770
  display: "flex",
648
- justifyContent: "space-between",
649
771
  alignItems: "center",
650
- gap: 16,
772
+ gap: 12,
651
773
  padding: "13px 16px",
652
774
  background: "none",
653
775
  border: "none",
@@ -659,12 +781,12 @@ var styles2 = {
659
781
  type: {
660
782
  fontSize: 13,
661
783
  fontWeight: 600,
662
- flex: 1,
663
- fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
784
+ overflow: "hidden",
785
+ textOverflow: "ellipsis",
786
+ whiteSpace: "nowrap",
787
+ fontFamily: MONO
664
788
  },
665
- chips: { display: "flex", flexWrap: "wrap", justifyContent: "flex-end", gap: 6 },
666
- chip,
667
- chipInactive: { ...chip, opacity: 0.5, textDecoration: "line-through" },
789
+ chips: { display: "flex", flexWrap: "wrap", gap: 6 },
668
790
  dot: { width: 6, height: 6, borderRadius: 999, background: "currentColor" },
669
791
  builtIn: {
670
792
  fontSize: 11,
@@ -672,7 +794,8 @@ var styles2 = {
672
794
  padding: "1px 7px",
673
795
  borderRadius: 999,
674
796
  border: "1px solid var(--fonderie-border,#e0e0e0)",
675
- color: "var(--fonderie-text-muted,#5c5c5c)"
797
+ color: "var(--fonderie-text-muted,#5c5c5c)",
798
+ flexShrink: 0
676
799
  },
677
800
  newButton: {
678
801
  display: "inline-flex",
@@ -708,7 +831,7 @@ import {
708
831
  } from "@fonderie/client";
709
832
  import { useTemplatePreview as useTemplatePreview2 } from "@fonderie/react-courier-admin";
710
833
  import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
711
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
834
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
712
835
  var TYPE_RE = /^[a-z0-9][a-z0-9_-]{0,80}$/;
713
836
  var LOCALE_RE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
714
837
  var IMPLICIT2 = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
@@ -717,6 +840,7 @@ function TemplateCreateScreen({
717
840
  client,
718
841
  type,
719
842
  locales = [],
843
+ defaultLocale,
720
844
  onCreated,
721
845
  locale
722
846
  }) {
@@ -736,7 +860,7 @@ function TemplateCreateScreen({
736
860
  const variables = useRef2([]);
737
861
  useEffect2(() => {
738
862
  if (!type) return;
739
- client.getTemplate(type, null).then(({ result }) => {
863
+ client.getTemplate(type, null).catch(() => client.getBuiltInTemplate(type, null)).then(({ result }) => {
740
864
  setSubject(result.subject ?? "");
741
865
  setHtml(result.html ?? "");
742
866
  setText(result.text);
@@ -779,6 +903,9 @@ function TemplateCreateScreen({
779
903
  if (!TYPE_RE.test(t)) return setError(tr("templates.create.errorType"));
780
904
  if (addingLocale && !l) return setError(tr("templates.create.errorLocaleRequired"));
781
905
  if (l && !LOCALE_RE.test(l)) return setError(tr("templates.create.errorLocale"));
906
+ if (l && defaultLocale && l.toLowerCase() === defaultLocale.toLowerCase()) {
907
+ return setError(tr("templates.create.errorDefaultLocale", { locale: defaultLocale }));
908
+ }
782
909
  if (!text.trim()) return setError(tr("templates.create.errorText"));
783
910
  setBusy(true);
784
911
  try {
@@ -808,7 +935,7 @@ function TemplateCreateScreen({
808
935
  /* @__PURE__ */ jsx3("p", { style: styles3.meta, children: addingLocale ? tr("templates.create.hintLocale") : tr("templates.create.hintNew") }),
809
936
  /* @__PURE__ */ jsxs3("div", { style: styles3.split, children: [
810
937
  /* @__PURE__ */ jsxs3("form", { style: styles3.form, onSubmit: (e) => void submit(e), children: [
811
- !addingLocale ? /* @__PURE__ */ jsxs3(Fragment, { children: [
938
+ !addingLocale ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
812
939
  /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: tr("templates.create.type") }),
813
940
  /* @__PURE__ */ jsx3(
814
941
  "input",
@@ -896,7 +1023,7 @@ function TemplateCreateScreen({
896
1023
  ] })
897
1024
  ] });
898
1025
  }
899
- var MONO = 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)';
1026
+ var MONO2 = 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)';
900
1027
  var styles3 = {
901
1028
  container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
902
1029
  // The editor's layout: the form on the left, the render pinned on the right.
@@ -929,7 +1056,7 @@ var styles3 = {
929
1056
  borderRadius: 8,
930
1057
  padding: 12,
931
1058
  fontSize: 13,
932
- fontFamily: MONO
1059
+ fontFamily: MONO2
933
1060
  },
934
1061
  // The console's warning token (the shell's amber), readable in both themes.
935
1062
  notice: {
@@ -982,7 +1109,7 @@ var styles3 = {
982
1109
  lineHeight: 1.55,
983
1110
  background: "var(--fonderie-surface,#fff)",
984
1111
  color: "var(--fonderie-text,#171717)",
985
- fontFamily: MONO
1112
+ fontFamily: MONO2
986
1113
  },
987
1114
  error: {
988
1115
  color: "var(--fonderie-danger,#e00)",