Instrument webhooks, fix compose layout, and ship social connect.
Deploy Beta / unit-tests (push) Successful in 10s
Deploy Beta / docker (push) Successful in 15s
Deploy Beta / deploy-beta (push) Successful in 1m40s

Add Grafana-friendly webhook request logging, Quill overflow fix, New Contact flow, in-place postcard campaign compose, and functional social account connect + composer.
This commit is contained in:
2026-08-10 05:15:49 -05:00
parent b3a6ee0cd0
commit 58258f2875
10 changed files with 778 additions and 311 deletions
@@ -4,19 +4,47 @@
{% block extra_head %}
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
<style>
.ql-editor { min-height: 160px; font-family: Georgia, "Times New Roman", serif; font-size: 15px; }
.ql-toolbar.ql-snow { border-color: var(--monica-border); border-radius: 4px 4px 0 0; }
.ql-container.ql-snow { border-color: var(--monica-border); border-radius: 0 0 4px 4px; background: #fff; }
#email-editor-wrap {
position: relative;
overflow: visible;
z-index: 1;
}
#email-editor {
display: flex;
flex-direction: column;
min-height: 200px;
}
.ql-toolbar.ql-snow {
border-color: var(--monica-border);
border-radius: 4px 4px 0 0;
flex-shrink: 0;
}
.ql-container.ql-snow {
border-color: var(--monica-border);
border-radius: 0 0 4px 4px;
background: #fff;
height: auto !important;
min-height: 160px;
flex: 1;
overflow: visible;
}
.ql-editor {
min-height: 160px;
font-family: Georgia, "Times New Roman", serif;
font-size: 15px;
}
#preview-body img { max-width: 100%; height: auto; }
#preview-body { line-height: 1.55; color: #212121; }
#email-editor-wrap[hidden], #sms-body-wrap[hidden] { display: none !important; }
#email-editor-wrap[hidden],
#sms-body-wrap[hidden],
#postcard-body-hint[hidden] { display: none !important; }
</style>
{% endblock %}
{% block portal_content %}
<div class="channel-tabs" id="compose-channel-tabs">
<a class="active" href="#compose-email" data-channel="email">Email</a>
<a href="#compose-sms" data-channel="sms">SMS</a>
<a href="{% url 'messaging:postcard_designer' %}" data-channel="postcard">Postcard</a>
<a href="#compose-postcard" data-channel="postcard">Postcard</a>
</div>
<div class="split">
@@ -58,6 +86,15 @@
oninput="syncSmsBody()">{{ form_data.body }}</textarea>
<div class="hint">Plain text for SMS · keep it short</div>
</div>
<div class="field" id="postcard-body-hint" hidden>
<p class="hint-block" style="margin:0">
Postcard campaigns use the selected PCM design. Optional note below is stored on the draft only.
</p>
<label for="id_body_pc" style="margin-top:8px">Internal note <span class="muted">(optional)</span></label>
<textarea id="id_body_pc" style="min-height:72px"
placeholder="Optional internal note…"
oninput="syncPostcardBody()">{{ form_data.body }}</textarea>
</div>
<div class="field" id="postcard-template-field">
<label for="id_template_id">Postcard design</label>
<select id="id_template_id" name="template_id">
@@ -67,10 +104,12 @@
{{ d.label }}
</option>
{% empty %}
<option value="" disabled>No designs yet — open Postcard design</option>
<option value="" disabled>No designs yet — create one first</option>
{% endfor %}
</select>
<div class="hint"><a href="{% url 'messaging:postcard_designer' %}">Open postcard designer</a> to create or edit designs</div>
<div class="hint" style="display:flex;gap:12px;flex-wrap:wrap;margin-top:8px">
<a class="btn btn-ghost btn-sm" href="{% url 'messaging:postcard_designer' %}">Create / update postcard design</a>
</div>
</div>
<div class="form-grid cols-2">
<div class="field">
@@ -165,14 +204,27 @@
syncCampaignPreview();
};
window.syncPostcardBody = function () {
var pc = document.getElementById('id_body_pc');
if (pc && bodyField) bodyField.value = pc.value;
syncCampaignPreview();
};
window.syncCampaignPreview = function () {
var subject = (document.getElementById('id_subject') || {}).value || '';
var audience = (document.getElementById('id_audience') || {}).value || '';
var isEmail = audience === 'email_opt_in';
var isPostcard = audience === 'postcard_opt_in';
var body = '';
if (isEmail && quill) {
body = quill.root.innerHTML;
if (body === '<p><br></p>' || body === '<p></p>') body = '';
} else if (isPostcard) {
var tmpl = document.getElementById('id_template_id');
var label = tmpl && tmpl.selectedIndex >= 0 ? tmpl.options[tmpl.selectedIndex].text : '';
body = label && tmpl.value ? ('Postcard design: ' + label) : '';
var note = (document.getElementById('id_body_pc') || {}).value || '';
if (note) body = (body ? body + '\n\n' : '') + note;
} else {
body = (bodyField && bodyField.value) || '';
}
@@ -188,7 +240,7 @@
}
empty.hidden = true;
content.hidden = false;
subEl.textContent = subject ? ('Subject: ' + subject) : '';
subEl.textContent = subject ? ('Subject: ' + subject) : (isPostcard ? 'Postcard mailing' : '');
if (isEmail) {
bodyEl.style.whiteSpace = 'normal';
bodyEl.innerHTML = body;
@@ -206,19 +258,24 @@
var tmplField = document.getElementById('postcard-template-field');
var emailWrap = document.getElementById('email-editor-wrap');
var smsWrap = document.getElementById('sms-body-wrap');
var pcHint = document.getElementById('postcard-body-hint');
var subjectField = document.getElementById('subject-field');
if (tmplField) tmplField.style.display = isPostcard ? '' : 'none';
if (subjectField) subjectField.style.display = isEmail ? '' : 'none';
if (emailWrap) emailWrap.hidden = !isEmail;
if (smsWrap) smsWrap.hidden = isEmail;
if (smsWrap) smsWrap.hidden = !isSms;
if (pcHint) pcHint.hidden = !isPostcard;
if (isEmail && quill) {
syncBodyFromQuill();
} else if (smsField && bodyField) {
// Keep SMS/postcard plain body in hidden field
if (!isEmail && smsField.value === '' && bodyField.value && bodyField.value.indexOf('<') === -1) {
} else if (isSms && smsField && bodyField) {
if (smsField.value === '' && bodyField.value && bodyField.value.indexOf('<') === -1) {
smsField.value = bodyField.value;
}
bodyField.value = smsField ? smsField.value : bodyField.value;
bodyField.value = smsField.value;
bodyField.removeAttribute('required');
} else if (isPostcard && bodyField) {
var pc = document.getElementById('id_body_pc');
bodyField.value = pc ? pc.value : '';
bodyField.removeAttribute('required');
}
document.querySelectorAll('#compose-channel-tabs a[data-channel]').forEach(function (a) {
@@ -288,21 +345,30 @@
document.getElementById('campaign-compose').addEventListener('submit', function () {
var audience = (document.getElementById('id_audience') || {}).value || '';
if (audience === 'email_opt_in') syncBodyFromQuill();
else if (smsField) bodyField.value = smsField.value;
else if (audience === 'sms_opt_in' && smsField) bodyField.value = smsField.value;
else if (audience === 'postcard_opt_in') {
var pc = document.getElementById('id_body_pc');
bodyField.value = pc ? pc.value : '';
}
});
}
document.querySelectorAll('#compose-channel-tabs a[data-channel="email"], #compose-channel-tabs a[data-channel="sms"]').forEach(function (a) {
document.querySelectorAll('#compose-channel-tabs a[data-channel]').forEach(function (a) {
a.addEventListener('click', function (e) {
e.preventDefault();
var ch = a.getAttribute('data-channel');
var audience = document.getElementById('id_audience');
if (!audience) return;
audience.value = ch === 'sms' ? 'sms_opt_in' : 'email_opt_in';
if (ch === 'sms') audience.value = 'sms_opt_in';
else if (ch === 'postcard') audience.value = 'postcard_opt_in';
else audience.value = 'email_opt_in';
syncComposeChannel();
});
});
var tmplSelect = document.getElementById('id_template_id');
if (tmplSelect) tmplSelect.addEventListener('change', syncCampaignPreview);
initQuill();
syncComposeChannel();
})();