Add merge tags for email/SMS, paginated removable recipients, PCM event panels for postcard campaigns, and a portal modal when phone/address matches an existing contact.
176 lines
7.5 KiB
HTML
176 lines
7.5 KiB
HTML
{% extends "portal_base.html" %}
|
|
{% load static %}
|
|
{% block title %}New contact · Portal{% endblock %}
|
|
{% block topbar_title %}New contact{% endblock %}
|
|
{% block extra_head %}
|
|
<link rel="stylesheet" href="{% static 'css/address-autocomplete.css' %}">
|
|
<style>
|
|
.portal-modal-backdrop {
|
|
position: fixed; inset: 0; background: rgba(17, 24, 39, 0.45);
|
|
display: flex; align-items: center; justify-content: center;
|
|
z-index: 1000; padding: 16px;
|
|
}
|
|
.portal-modal {
|
|
background: #fff; border: 1px solid var(--monica-border);
|
|
max-width: 480px; width: 100%; padding: 20px 22px;
|
|
box-shadow: 0 12px 40px rgba(0,0,0,0.18);
|
|
}
|
|
.portal-modal h3 { margin: 0 0 8px; font-size: 18px; }
|
|
.portal-modal p { margin: 0 0 12px; font-size: 14px; color: var(--monica-muted); line-height: 1.45; }
|
|
.portal-modal .match-card {
|
|
background: #f8fafc; border: 1px solid var(--monica-border);
|
|
padding: 12px; margin: 0 0 16px; font-size: 14px;
|
|
}
|
|
.portal-modal .match-card strong { display: block; margin-bottom: 4px; }
|
|
.portal-modal-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
</style>
|
|
{% endblock %}
|
|
{% block portal_content %}
|
|
<form method="post" id="contact-create-form">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="resolve_match" id="id_resolve_match" value="">
|
|
<input type="hidden" name="match_id" id="id_match_id" value="{% if match_prompt %}{{ match_prompt.contact.pk }}{% endif %}">
|
|
<div class="split">
|
|
<div class="panel">
|
|
<div class="panel-h"><h2>Profile</h2></div>
|
|
<div class="panel-b form-grid">
|
|
<div class="form-grid cols-2">
|
|
<div class="field">
|
|
<label for="id_first_name">First name</label>
|
|
<input id="id_first_name" name="first_name" required value="{{ form.first_name }}">
|
|
</div>
|
|
<div class="field">
|
|
<label for="id_last_name">Last name</label>
|
|
<input id="id_last_name" name="last_name" value="{{ form.last_name }}">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div class="field">
|
|
<label for="id_email">Email</label>
|
|
<input id="id_email" name="email" type="email" required value="{{ form.email }}">
|
|
</div>
|
|
<div class="field">
|
|
<label for="id_phone">Phone</label>
|
|
<input id="id_phone" name="phone" value="{{ form.phone }}">
|
|
</div>
|
|
</div>
|
|
|
|
<div data-address-autocomplete data-suggest-url="{% url 'address_suggest' %}">
|
|
<div class="field address-ac-wrap">
|
|
<label>Street address</label>
|
|
<input name="address_line1" data-ac="line1" value="{{ form.address_line1 }}" autocomplete="off">
|
|
</div>
|
|
<div class="field">
|
|
<label>Apt / suite</label>
|
|
<input name="address_line2" data-ac="line2" value="{{ form.address_line2 }}" autocomplete="address-line2">
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div class="field">
|
|
<label>City</label>
|
|
<input name="address_city" data-ac="city" value="{{ form.address_city }}" autocomplete="address-level2">
|
|
</div>
|
|
<div class="field">
|
|
<label>State</label>
|
|
<input name="address_state" data-ac="state" value="{{ form.address_state }}" autocomplete="address-level1" maxlength="32">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div class="field">
|
|
<label>ZIP</label>
|
|
<input name="address_zip" data-ac="zip" value="{{ form.address_zip }}" autocomplete="postal-code" maxlength="20">
|
|
</div>
|
|
<div class="field">
|
|
<label>Country</label>
|
|
<input name="address_country" data-ac="country" value="{{ form.address_country|default:'US' }}" autocomplete="country" maxlength="2">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="id_notes">Notes</label>
|
|
<textarea id="id_notes" name="notes">{{ form.notes }}</textarea>
|
|
</div>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
|
<button class="btn btn-primary btn-sm" type="submit">Add to mailing list</button>
|
|
<a class="btn btn-ghost btn-sm" href="{% url 'contacts:list' %}">Cancel</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel">
|
|
<div class="panel-h"><h2>Consent</h2></div>
|
|
<div class="panel-b">
|
|
<div class="field">
|
|
<label class="check-row"><input type="checkbox" name="consent_email" value="1" {% if form.consent_email %}checked{% endif %}> Email marketing</label>
|
|
</div>
|
|
<div class="field">
|
|
<label class="check-row"><input type="checkbox" name="consent_sms" value="1" {% if form.consent_sms %}checked{% endif %}> SMS updates</label>
|
|
</div>
|
|
<div class="field">
|
|
<label class="check-row"><input type="checkbox" name="consent_postcard" value="1" {% if form.consent_postcard %}checked{% endif %}> Postcard mailings</label>
|
|
</div>
|
|
<p class="hint-block" style="margin-top:16px">
|
|
Same email always updates that contact. Same phone or address asks whether to update or create new.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
{% if match_prompt %}
|
|
<div class="portal-modal-backdrop" id="match-modal" role="dialog" aria-modal="true" aria-labelledby="match-modal-title">
|
|
<div class="portal-modal">
|
|
<h3 id="match-modal-title">Possible duplicate</h3>
|
|
<p>
|
|
An existing contact shares this {{ match_prompt.reason_label }}.
|
|
Update that record, or create a separate contact anyway?
|
|
</p>
|
|
<div class="match-card">
|
|
<strong>{{ match_prompt.contact }}</strong>
|
|
{% if match_prompt.contact.email %}<div>{{ match_prompt.contact.email }}</div>{% endif %}
|
|
{% if match_prompt.contact.phone %}<div>{{ match_prompt.contact.phone }}</div>{% endif %}
|
|
{% if match_prompt.contact.postal_address.line1 %}
|
|
<div class="muted">
|
|
{{ match_prompt.contact.postal_address.line1 }}{% if match_prompt.contact.postal_address.city %}, {{ match_prompt.contact.postal_address.city }}{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
<div style="margin-top:8px">
|
|
<a href="{% url 'contacts:detail' match_prompt.contact.pk %}" target="_blank" rel="noopener">Open existing contact</a>
|
|
</div>
|
|
</div>
|
|
<div class="portal-modal-actions">
|
|
<button class="btn btn-primary" type="button" id="match-update">Update existing</button>
|
|
<button class="btn btn-ghost" type="button" id="match-create">Create new contact</button>
|
|
<button class="btn btn-ghost" type="button" id="match-cancel">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|
|
{% block extra_js %}
|
|
<script src="{% static 'js/address-autocomplete.js' %}"></script>
|
|
{% if match_prompt %}
|
|
<script>
|
|
(function () {
|
|
var form = document.getElementById('contact-create-form');
|
|
var resolve = document.getElementById('id_resolve_match');
|
|
var modal = document.getElementById('match-modal');
|
|
function submitWith(choice) {
|
|
if (resolve) resolve.value = choice;
|
|
if (modal) modal.hidden = true;
|
|
form.submit();
|
|
}
|
|
document.getElementById('match-update')?.addEventListener('click', function () {
|
|
submitWith('update');
|
|
});
|
|
document.getElementById('match-create')?.addEventListener('click', function () {
|
|
submitWith('create');
|
|
});
|
|
document.getElementById('match-cancel')?.addEventListener('click', function () {
|
|
if (resolve) resolve.value = '';
|
|
if (modal) modal.remove();
|
|
});
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|