diff --git a/templates/classifieds/edit.html b/templates/classifieds/edit.html
index aa2c4ae..d23ecfb 100644
--- a/templates/classifieds/edit.html
+++ b/templates/classifieds/edit.html
@@ -51,11 +51,13 @@
.quill-container .ql-toolbar { border-top-left-radius: var(--radius); border-top-right-radius: var(--radius); }
.quill-container .ql-container { border-bottom-left-radius: var(--radius); border-bottom-right-radius: var(--radius); font-size: var(--font-size-base); }
.quill-container .ql-editor { min-height: 150px; }
- .field-error, input.field-error, .quill-container.field-error {
+ input.field-error, .quill-container.field-error {
border: 2px solid #dc2626 !important;
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}
- :invalid:not(:focus):not(:placeholder-shown) { border: 2px solid #dc2626; }
+ .form-group.field-valid > label::after {
+ content: " ✓"; color: #16a34a; font-weight: 700; margin-left: 4px;
+ }
/* Existing attachments */
.existing-attachment { position: relative; border: 1px solid var(--border); border-radius: var(--radius); padding: var(--spacing-xs); background: var(--surface); }
@@ -192,7 +194,25 @@ quill.root.innerHTML = {{ classified.description|tojson }};
(function() {
var qc = document.getElementById('quill-editor');
- quill.on('text-change', function() { qc && qc.classList.remove('field-error'); });
+ function setValid(g, ok) {
+ if (!g) return;
+ if (ok) { g.classList.add('field-valid'); g.classList.remove('field-error'); }
+ else g.classList.remove('field-valid');
+ }
+ var titleEl = document.getElementById('title');
+ var titleGrp = titleEl.closest('.form-group');
+ var descGrp = qc ? qc.closest('.form-group') : null;
+ function refreshTitle() { setValid(titleGrp, titleEl.value.trim().length > 0); }
+ function refreshDesc() {
+ var html = quill.root.innerHTML;
+ var ok = !(html === '
' || quill.getText().trim() === '');
+ setValid(descGrp, ok);
+ if (ok && qc) qc.classList.remove('field-error');
+ }
+ titleEl.addEventListener('input', refreshTitle);
+ quill.on('text-change', refreshDesc);
+ refreshTitle(); refreshDesc();
+
document.getElementById('classifiedForm').addEventListener('submit', function(e) {
var html = quill.root.innerHTML;
var empty = (html === '
' || quill.getText().trim() === '');
diff --git a/templates/classifieds/new.html b/templates/classifieds/new.html
index 41b2c5f..81df2f2 100755
--- a/templates/classifieds/new.html
+++ b/templates/classifieds/new.html
@@ -25,9 +25,8 @@
.quill-container .ql-editor {
min-height: 150px;
}
- /* Highlight required fields that failed validation. Applied by server
- on POST validation error and by client JS on Quill empty submit. */
- .field-error,
+ /* Red border on required fields that failed validation. Applied by
+ server on POST error and by client JS on submit-with-empty Quill. */
input.field-error,
select.field-error,
.type-selector.field-error,
@@ -35,9 +34,13 @@
border: 2px solid #dc2626 !important;
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}
- :invalid:not(:focus):not(:placeholder-shown),
- select:invalid:not(:focus) {
- border: 2px solid #dc2626;
+ /* Green checkmark next to label of filled required fields. JS toggles
+ .field-valid on the form-group as the user types/picks. */
+ .form-group.field-valid > label::after {
+ content: " ✓";
+ color: #16a34a;
+ font-weight: 700;
+ margin-left: 4px;
}
.form-container {
max-width: 700px;
@@ -390,8 +393,47 @@ var quill = new Quill('#quill-editor', {
// Restore from server-rendered textarea (e.g. after POST validation error)
var initialDesc = document.getElementById('description').value;
if (initialDesc) { quill.root.innerHTML = initialDesc; }
- // Clear error highlight as soon as user starts typing
- quill.on('text-change', function() { qc && qc.classList.remove('field-error'); });
+
+ // Toggle .field-valid on a .form-group based on a "is filled" check.
+ function setValid(group, ok) {
+ if (!group) return;
+ if (ok) {
+ group.classList.add('field-valid');
+ group.classList.remove('field-error');
+ } else {
+ group.classList.remove('field-valid');
+ }
+ }
+
+ var titleEl = document.getElementById('title');
+ var titleGrp = titleEl.closest('.form-group');
+ var catEl = document.getElementById('category');
+ var catGrp = catEl.closest('.form-group');
+ var radios = document.querySelectorAll('input[name="listing_type"]');
+ var radiosGrp = radios.length ? radios[0].closest('.form-group') : null;
+ var descGrp = qc ? qc.closest('.form-group') : null;
+
+ function refreshTitle() { setValid(titleGrp, titleEl.value.trim().length > 0); }
+ function refreshCat() { setValid(catGrp, catEl.value !== ''); }
+ function refreshRadios() {
+ var any = Array.from(radios).some(function(r) { return r.checked; });
+ setValid(radiosGrp, any);
+ }
+ function refreshDesc() {
+ var html = quill.root.innerHTML;
+ var ok = !(html === '
' || quill.getText().trim() === '');
+ setValid(descGrp, ok);
+ if (ok && qc) qc.classList.remove('field-error');
+ }
+
+ titleEl.addEventListener('input', refreshTitle);
+ catEl.addEventListener('change', refreshCat);
+ radios.forEach(function(r) { r.addEventListener('change', refreshRadios); });
+ quill.on('text-change', refreshDesc);
+
+ // Initial pass — pre-filled fields (after POST validation error) get
+ // their green check immediately on page load.
+ refreshTitle(); refreshCat(); refreshRadios(); refreshDesc();
document.getElementById('classifiedForm').addEventListener('submit', function(e) {
var html = quill.root.innerHTML;