2026-03-03 10:20:07 -06:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
< title > Signature Manager — Template Editor< / title >
< style >
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; background: #1a1a1a; color: #eee; }
header { background: #111; border-bottom: 2px solid #C9A84C; padding: 14px 24px; display: flex; align-items: center; gap: 16px; }
header h1 { font-size: 18px; color: #C9A84C; }
nav a { color: #ccc; text-decoration: none; margin-left: 16px; font-size: 14px; }
nav a:hover { color: #C9A84C; }
main { padding: 24px; max-width: 1200px; margin: auto; }
.layout { display: flex; gap: 20px; flex-wrap: wrap; }
.panel { flex: 1; min-width: 300px; }
.panel h2 { font-size: 14px; color: #C9A84C; margin-bottom: 10px; }
2026-03-03 16:23:30 -06:00
textarea { width: 100%; height: 380px; background: #222; color: #eee; border: 1px solid #444; border-radius: 6px; padding: 12px; font-family: monospace; font-size: 12px; resize: vertical; }
2026-03-03 10:20:07 -06:00
.preview-box { background: #fff; border-radius: 6px; padding: 20px; min-height: 120px; border: 1px solid #444; }
.actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
button { background: #C9A84C; color: #111; border: none; padding: 10px 20px; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 14px; }
button:hover { background: #e0bc5c; }
button.secondary { background: #333; color: #eee; border: 1px solid #555; }
button.secondary:hover { background: #444; }
#status-msg { margin-top: 12px; padding: 10px 14px; border-radius: 6px; display: none; }
#status-msg.ok { background: #1a4a1a; color: #4caf50; display: block; }
#status-msg.err { background: #4a1a1a; color: #f44336; display: block; }
.test-fields { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.test-fields input { background: #2a2a2a; border: 1px solid #444; color: #eee; padding: 6px 10px; border-radius: 4px; font-size: 12px; width: calc(50% - 4px); }
2026-03-03 16:23:30 -06:00
.logo-row { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
.logo-row input { flex: 1; background: #2a2a2a; border: 1px solid #444; color: #eee; padding: 6px 10px; border-radius: 4px; font-size: 12px; }
.logo-row label { font-size: 12px; color: #aaa; white-space: nowrap; }
2026-03-13 13:43:13 -05:00
.version-panel { background: #222; border: 1px solid #444; border-radius: 6px; padding: 16px; margin-bottom: 20px; }
.version-list { margin-top: 10px; max-height: 200px; overflow-y: auto; }
.version-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #333; font-size: 13px; }
.version-item:hover { background: #2a2a2a; }
.version-info { flex: 1; cursor: pointer; }
.version-name { font-weight: bold; color: #C9A84C; }
.version-date { font-size: 11px; color: #777; margin-top: 2px; }
.version-actions { display: flex; gap: 8px; }
.v-btn { padding: 4px 8px; font-size: 11px; }
.save-form { display: flex; gap: 8px; margin-top: 10px; }
.save-form input { flex: 1; background: #333; border: 1px solid #444; color: #eee; padding: 8px; border-radius: 4px; font-size: 13px; }
2026-03-03 10:20:07 -06:00
< / style >
< / head >
< body >
< header >
< h1 > ✉ Email Signature Manager< / h1 >
< nav >
< a href = "/dashboard" > Dashboard< / a >
< a href = "/editor" > Template Editor< / a >
< / nav >
< / header >
< main >
< div class = "layout" >
< div class = "panel" >
2026-03-13 13:43:13 -05:00
< div class = "version-panel" >
< h2 > Saved Versions< / h2 >
< div class = "save-form" >
< input id = "v-name" placeholder = "Version Name (e.g. Holiday 2026)" / >
< button onclick = "saveVersion()" class = "v-btn" > Save As Version< / button >
< / div >
< div class = "version-list" id = "version-list" >
< div style = "color:#777; padding: 10px;" > Loading versions...< / div >
< / div >
< / div >
2026-03-03 10:20:07 -06:00
< h2 > Handlebars Template (HTML)< / h2 >
< textarea id = "template-editor" spellcheck = "false" > < / textarea >
< div class = "actions" >
< button onclick = "saveTemplate()" > 💾 Save Template< / button >
< button class = "secondary" onclick = "loadTemplate()" > ↻ Reset< / button >
< / div >
< / div >
< div class = "panel" >
< h2 > Live Preview< / h2 >
< div class = "test-fields" >
2026-03-03 16:23:30 -06:00
< input id = "p-name" placeholder = "Full Name" value = "Jason Stedwell" oninput = "updatePreview()" / >
< input id = "p-title" placeholder = "Job Title" value = "Director of Technical Services" oninput = "updatePreview()" / >
< input id = "p-email" placeholder = "Email" value = "jason@messagepoint.tv" oninput = "updatePreview()" / >
< input id = "p-phone" placeholder = "Office Phone" value = "205-719-5000" oninput = "updatePreview()" / >
< input id = "p-cell" placeholder = "Cell (optional)" value = "334-707-2550" oninput = "updatePreview()" / >
< / div >
< div class = "logo-row" >
< label > Logo URL:< / label >
< input id = "p-logo" value = "https://alwisp.com/uploads/logo.png" oninput = "updatePreview()" / >
2026-03-03 10:20:07 -06:00
< / div >
< div class = "preview-box" id = "preview-frame" > Loading preview...< / div >
< div class = "actions" >
< button class = "secondary" onclick = "updatePreview()" > ↻ Refresh Preview< / button >
< / div >
< / div >
< / div >
< div id = "status-msg" > < / div >
< / main >
< script >
async function loadTemplate() {
const res = await fetch('/api/admin/template').then(r=>r.json());
document.getElementById('template-editor').value = res.content;
updatePreview();
}
async function saveTemplate() {
const content = document.getElementById('template-editor').value;
const res = await fetch('/api/admin/template',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({content})}).then(r=>r.json());
2026-03-13 13:43:13 -05:00
showStatus(res.ok ? 'Template deployed! A .bak backup was created.' : 'Error: '+res.error, res.ok);
updatePreview();
}
async function loadVersions() {
const versions = await fetch('/api/admin/versions').then(r=>r.json()).catch(()=>[]);
const list = document.getElementById('version-list');
if (!versions.length) {
list.innerHTML = '< div style = "color:#777; padding:10px;" > No saved versions yet.< / div > ';
return;
}
list.innerHTML = versions.map(v => `
< div class = "version-item" >
< div class = "version-info" onclick = "loadVersionData(${v.id})" >
< div class = "version-name" > ${v.name}< / div >
< div class = "version-date" > ${new Date(v.updated_at).toLocaleString()}< / div >
< / div >
< div class = "version-actions" >
< button class = "secondary v-btn" onclick = "deployVersion(${v.id})" > Deploy< / button >
< / div >
< / div >
`).join('');
}
async function saveVersion() {
const name = document.getElementById('v-name').value.trim();
const content = document.getElementById('template-editor').value;
if (!name) return alert('Enter a version name.');
const res = await fetch('/api/admin/versions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, content })
}).then(r=>r.json());
if (res.ok) {
showStatus('Version saved!', true);
document.getElementById('v-name').value = '';
loadVersions();
} else {
showStatus('Error: '+res.error, false);
}
}
async function loadVersionData(id) {
const v = await fetch(`/api/admin/versions/${id}`).then(r=>r.json());
document.getElementById('template-editor').value = v.content;
document.getElementById('v-name').value = v.name;
2026-03-03 10:20:07 -06:00
updatePreview();
}
2026-03-13 13:43:13 -05:00
async function deployVersion(id) {
if (!confirm('Set this version as the ACTIVE template?')) return;
const res = await fetch(`/api/admin/versions/${id}/deploy`, { method: 'POST' }).then(r=>r.json());
if (res.ok) {
showStatus('Version deployed and active!', true);
} else {
showStatus('Error: '+res.error, false);
}
}
2026-03-03 10:20:07 -06:00
async function updatePreview() {
const templateHtml = document.getElementById('template-editor').value;
const userData = {
2026-03-03 16:23:30 -06:00
fullName: document.getElementById('p-name').value,
title: document.getElementById('p-title').value,
email: document.getElementById('p-email').value,
phone: document.getElementById('p-phone').value,
cellPhone: document.getElementById('p-cell').value,
logoUrl: document.getElementById('p-logo').value
2026-03-03 10:20:07 -06:00
};
const res = await fetch('/api/admin/preview',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({templateHtml,userData})}).then(r=>r.json()).catch(()=>({error:'Preview failed'}));
document.getElementById('preview-frame').innerHTML = res.html || `< span style = "color:red" > ${res.error}< / span > `;
}
function showStatus(msg, ok) {
const el = document.getElementById('status-msg');
2026-03-03 16:23:30 -06:00
el.textContent = msg; el.className = ok ? 'ok' : 'err'; el.style.display = 'block';
setTimeout(()=>{ el.style.display='none'; }, 5000);
2026-03-03 10:20:07 -06:00
}
loadTemplate();
2026-03-13 13:43:13 -05:00
loadVersions();
2026-03-03 10:20:07 -06:00
< / script >
< / body >
< / html >