Files
ihasmail/web/src/views/settings/TemplatesSettings.tsx
T
jcoffey-dev 645b8b510f ihasmail 2.0: rebuild as Stalwart-first JMAP webmail
Replace the FastAPI/HTMX prototype with a Node/Hono session proxy and a
React 19/Vite SPA. Mail (conversation view, search operators, labels,
sanitised HTML, privacy image proxy, invites, undo send, templates),
calendar (month/week/day/agenda, invites, free/busy, categories,
context menus), contacts (JSContact, groups, vCard), files, Sieve filter
builder (incl. filter-from-message with retroactive apply), vacation,
identities with default + Reply-To, PWA/mobile layout, push via SSE,
in-memory mock Stalwart for dev, Docker + CI.
2026-08-23 01:07:13 -07:00

44 lines
2.8 KiB
TypeScript

import { useState } from "react";
import { Plus, Trash2 } from "lucide-react";
import { useSettings, type Template } from "@/store/settings";
import { Dialog } from "@/ui/dialog";
import { RichEditor } from "../compose/RichEditor";
import { htmlToText } from "@/lib/text";
export function TemplatesSettings() {
const templates = useSettings((s) => s.settings.templates);
const update = useSettings((s) => s.update);
const [editing, setEditing] = useState<Template | null>(null);
return (
<div>
<h1>Templates</h1>
<p className="lead">Canned responses you can insert into any message from the composer's template button.</p>
{templates.map((t) => (
<div key={t.id} className="card clickable" onClick={() => setEditing(t)}>
<div className="card-head">
<h3>{t.name}</h3>
<button className="icon-btn sm danger" aria-label="Delete template" onClick={(e) => { e.stopPropagation(); update({ templates: templates.filter((x) => x.id !== t.id) }); }}><Trash2 size={16} /></button>
</div>
{t.subject && <div className="hint">Subject: {t.subject}</div>}
<div className="hint truncate">{htmlToText(t.html).slice(0, 140)}</div>
</div>
))}
<button className="btn" onClick={() => setEditing({ id: `t${Date.now()}`, name: "", subject: "", html: "" })}><Plus size={16} /> New template</button>
{editing && (
<Dialog open onClose={() => setEditing(null)} title={templates.some((t) => t.id === editing.id) ? "Edit template" : "New template"} size="lg" footer={<><button className="btn" onClick={() => setEditing(null)}>Cancel</button><button className="btn btn-primary" disabled={!editing.name.trim()} onClick={() => { const exists = templates.some((t) => t.id === editing.id); update({ templates: exists ? templates.map((t) => (t.id === editing.id ? editing : t)) : [...templates, editing] }); setEditing(null); }}>Save</button></>}>
<div className="field-row">
<div className="field"><label>Name</label><input className="input" value={editing.name} onChange={(e) => setEditing({ ...editing, name: e.target.value })} autoFocus /></div>
<div className="field"><label>Subject (optional)</label><input className="input" value={editing.subject} onChange={(e) => setEditing({ ...editing, subject: e.target.value })} /></div>
</div>
<div className="field">
<label>Body</label>
<div style={{ border: "1px solid var(--border-strong)", borderRadius: 8, minHeight: 200, display: "flex", flexDirection: "column" }}>
<RichEditor html={editing.html} onChange={(html) => setEditing({ ...editing, html })} showToolbar placeholder="Template text…" />
</div>
</div>
</Dialog>
)}
</div>
);
}