From 9d5905e68949db81f14034d3fc320ce36256cddf Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Tue, 14 Apr 2026 13:33:18 +0200 Subject: [PATCH] fix(classifieds): preserve form values + red border on missing fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously when server validation failed (e.g. missing required field), the whole form re-rendered with all values cleared — user had to retype everything. Also Quill empty-content showed an alert dialog. Now: - Server-side: form_data + missing_fields passed to template; values re-populate inputs, missing fields get .field-error class (red border) - Quill empty: red border on the editor container instead of alert, cleared as soon as user starts typing - Other required fields (radio, select, title): same .field-error treatment plus :invalid CSS for live HTML5 feedback Co-Authored-By: Claude Opus 4.6 (1M context) --- blueprints/community/classifieds/routes.py | 14 +++- templates/classifieds/edit.html | 36 +++++++---- templates/classifieds/new.html | 74 +++++++++++++++------- 3 files changed, 86 insertions(+), 38 deletions(-) diff --git a/blueprints/community/classifieds/routes.py b/blueprints/community/classifieds/routes.py index 21751df..bcd77fd 100644 --- a/blueprints/community/classifieds/routes.py +++ b/blueprints/community/classifieds/routes.py @@ -93,7 +93,13 @@ def new(): if not listing_type or not category or not title or not description: flash('Wszystkie wymagane pola muszą być wypełnione.', 'error') - return render_template('classifieds/new.html') + return render_template('classifieds/new.html', form_data=request.form, + missing_fields={ + 'listing_type': not listing_type, + 'category': not category, + 'title': not title, + 'description': not description, + }) db = SessionLocal() try: @@ -276,7 +282,11 @@ def edit(classified_id): if not classified.title or not classified.description: flash('Tytuł i opis są wymagane.', 'error') - return render_template('classifieds/edit.html', classified=classified) + return render_template('classifieds/edit.html', classified=classified, + missing_fields={ + 'title': not classified.title, + 'description': not classified.description, + }) # Handle deleted attachments delete_ids = request.form.getlist('delete_attachments[]') diff --git a/templates/classifieds/edit.html b/templates/classifieds/edit.html index 6ac5a4f..39d3147 100644 --- a/templates/classifieds/edit.html +++ b/templates/classifieds/edit.html @@ -51,6 +51,11 @@ .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 { + 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; } /* Existing attachments */ .existing-attachment { position: relative; border: 1px solid var(--border); border-radius: var(--radius); padding: var(--spacing-xs); background: var(--surface); } @@ -114,12 +119,12 @@
- +
-
+
@@ -185,16 +190,23 @@ var quill = new Quill('#quill-editor', { }); quill.root.innerHTML = {{ classified.description|tojson }}; -document.querySelector('form').addEventListener('submit', function(e) { - var html = quill.root.innerHTML; - if (html === '


' || quill.getText().trim() === '') { - e.preventDefault(); - alert('Wpisz treść ogłoszenia.'); - quill.focus(); - return; - } - document.getElementById('description').value = html; -}); +(function() { + var qc = document.getElementById('quill-editor'); + quill.on('text-change', function() { qc && qc.classList.remove('field-error'); }); + document.querySelector('form').addEventListener('submit', function(e) { + var html = quill.root.innerHTML; + var empty = (html === '


' || quill.getText().trim() === ''); + if (empty) { + e.preventDefault(); + qc && qc.classList.add('field-error'); + qc && qc.scrollIntoView({behavior: 'smooth', block: 'center'}); + quill.focus(); + return; + } + qc && qc.classList.remove('field-error'); + document.getElementById('description').value = html; + }); +})(); function toggleDeleteAttachment(attId) { var el = document.getElementById('existing-' + attId); diff --git a/templates/classifieds/new.html b/templates/classifieds/new.html index 12b08e0..42988bd 100755 --- a/templates/classifieds/new.html +++ b/templates/classifieds/new.html @@ -25,6 +25,20 @@ .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, + input.field-error, + select.field-error, + .type-selector.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), + select:invalid:not(:focus) { + border: 2px solid #dc2626; + } .form-container { max-width: 700px; margin: 0 auto; @@ -279,9 +293,9 @@
-
+
- +
- +