From 19e35264308746c63aadf41b074ef4502d8829d6 Mon Sep 17 00:00:00 2001 From: Gelu Mihes Date: Tue, 12 May 2026 11:11:06 +0300 Subject: [PATCH] Changes --- web/wwwroot/css/myai.css | 72 +++++++++++++++++++---- web/wwwroot/cv-matcher/index.html | 10 +++- web/wwwroot/index.html | 7 ++- web/wwwroot/js/main.js | 98 +++++++++++++++++++++++-------- 4 files changed, 148 insertions(+), 39 deletions(-) diff --git a/web/wwwroot/css/myai.css b/web/wwwroot/css/myai.css index 01e9d00..85f5877 100644 --- a/web/wwwroot/css/myai.css +++ b/web/wwwroot/css/myai.css @@ -307,29 +307,81 @@ img { font-weight: 700 } - .ai-panel input, .ai-panel textarea, .contact-form input, .contact-form textarea { + .ai-panel input:not([type="checkbox"]):not([type="file"]), + .ai-panel textarea, + .contact-form input:not([type="checkbox"]):not([type="file"]), + .contact-form textarea, + .subscribe-form input[type="email"] { width: 100%; - border: 1px solid rgba(255,255,255,.12); - border-radius: 18px; - background: rgba(0,0,0,.25); - color: #fff; - padding: 15px 16px; - outline: none + border: 1px solid #d9e1f0; + border-radius: 6px; + background: #fff; + color: #0e1e3a; + padding: 12px 14px; + outline: none; + font-family: inherit; + font-size: 1rem; + line-height: 1.4; + transition: border-color .15s ease, box-shadow .15s ease } - .ai-panel input:focus, .ai-panel textarea:focus, .contact-form input:focus, .contact-form textarea:focus { - border-color: rgba(95,160,255,.65) + .ai-panel input:not([type="checkbox"]):not([type="file"])::placeholder, + .ai-panel textarea::placeholder, + .contact-form input:not([type="checkbox"]):not([type="file"])::placeholder, + .contact-form textarea::placeholder, + .subscribe-form input[type="email"]::placeholder { + color: #97a4b8 } + .ai-panel input:not([type="checkbox"]):not([type="file"]):focus, + .ai-panel textarea:focus, + .contact-form input:not([type="checkbox"]):not([type="file"]):focus, + .contact-form textarea:focus, + .subscribe-form input[type="email"]:focus { + border-color: #5fa0ff; + box-shadow: 0 0 0 3px rgba(95,160,255,.18) + } + + .ai-panel label.is-invalid input:not([type="checkbox"]):not([type="file"]), + .ai-panel label.is-invalid textarea, + .contact-form label.is-invalid input:not([type="checkbox"]):not([type="file"]), + .contact-form label.is-invalid textarea, + .subscribe-form .subscribe-row.is-invalid input[type="email"] { + border-color: #ff8a8a; + box-shadow: 0 0 0 3px rgba(255,138,138,.18) + } + +.field-error { + display: block; + margin-top: 6px; + color: #ff8a8a; + font-size: .85rem; + font-weight: 600; + line-height: 1.35 +} + +.field-error:empty { + display: none +} + +.consent-inline.is-invalid label { + color: #ff8a8a +} + .file-drop { display: block; border: 1px dashed rgba(143,184,255,.45); - border-radius: 22px; + border-radius: 6px; background: rgba(95,160,255,.07); padding: 22px; cursor: pointer } +.file-drop.is-invalid { + border-color: #ff8a8a; + background: rgba(255,138,138,.06) +} + .file-drop input { display: none } diff --git a/web/wwwroot/cv-matcher/index.html b/web/wwwroot/cv-matcher/index.html index 1f3a4af..1cce046 100644 --- a/web/wwwroot/cv-matcher/index.html +++ b/web/wwwroot/cv-matcher/index.html @@ -89,7 +89,7 @@
-
+ Input

CV and job details

+
+ @@ -149,18 +152,21 @@ -
+ diff --git a/web/wwwroot/index.html b/web/wwwroot/index.html index 9355719..ce06f7c 100644 --- a/web/wwwroot/index.html +++ b/web/wwwroot/index.html @@ -139,18 +139,21 @@
- + @@ -162,10 +165,12 @@
+ +
diff --git a/web/wwwroot/js/main.js b/web/wwwroot/js/main.js index 5c98128..616d12b 100644 --- a/web/wwwroot/js/main.js +++ b/web/wwwroot/js/main.js @@ -48,6 +48,10 @@ "form.captchaFailed": "Captcha verification failed.", "form.failed": "Failed to send the message.", "form.rateLimited": "Too many requests from your network. Please wait a moment and try again.", + "form.required.name": "Please enter your name.", + "form.required.email": "Please enter your email address.", + "form.required.emailInvalid": "Please enter a valid email address.", + "form.required.message": "Please write a message.", "subscribe.title": "Stay in the loop", "subscribe.text": "Get a short note when a new AI demo is published. One email at most every few weeks.", "subscribe.emailPlaceholder": "name@company.com", @@ -153,6 +157,10 @@ "form.captchaFailed": "Verificarea Captcha a eșuat.", "form.failed": "Mesajul nu a putut fi trimis.", "form.rateLimited": "Prea multe cereri din rețeaua ta. Te rugăm să aștepți câteva momente și să încerci din nou.", + "form.required.name": "Te rugăm să introduci numele.", + "form.required.email": "Te rugăm să introduci adresa de email.", + "form.required.emailInvalid": "Te rugăm să introduci o adresă de email validă.", + "form.required.message": "Te rugăm să scrii un mesaj.", "subscribe.title": "Rămâi la curent", "subscribe.text": "Primești o notă scurtă când publicăm un nou demo AI. Cel mult un email la câteva săptămâni.", "subscribe.emailPlaceholder": "nume@companie.ro", @@ -393,6 +401,27 @@ $('#msgSubmit').removeClass().addClass('form-message ' + tone).text(msg); } + function showFieldError(errorId, msg) { + var $el = $('#' + errorId); + $el.text(msg || ''); + // Look first at the parent (for errors nested inside a label), + // then at the previous sibling block (for errors after consent/file-drop/subscribe-row). + var $parent = $el.parent(); + var $container = $parent.is('label, .consent-inline') ? $parent : $el.prev(); + if (!$container.length || !$container.is('label, .consent-inline, .file-drop, .subscribe-row')) { + $container = $el.closest('form'); + } + $container.toggleClass('is-invalid', !!msg); + } + + function clearFieldErrors(errorIds) { + for (var i = 0; i < errorIds.length; i++) showFieldError(errorIds[i], ''); + } + + function isValidEmail(value) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value || '').trim()); + } + function formSuccess() { $('#contactForm')[0].reset(); submitMSG(true, t('form.thanks')); @@ -408,22 +437,35 @@ event.preventDefault(); var loader = $('#contactLoader'), button = $('#submit'); + + var name = $('#name').val().trim(); + var email = $('#email').val().trim(); + var message = $('#message').val().trim(); + clearFieldErrors(['nameError', 'emailError', 'messageError']); + $('#msgSubmit').removeClass().addClass('form-message').text(''); + + var hasError = false; + if (!name) { showFieldError('nameError', t('form.required.name')); hasError = true; } + if (!email) { showFieldError('emailError', t('form.required.email')); hasError = true; } + else if (!isValidEmail(email)) { showFieldError('emailError', t('form.required.emailInvalid')); hasError = true; } + if (!message) { showFieldError('messageError', t('form.required.message')); hasError = true; } + if (hasError) return; + loader.css('display', 'flex'); button.prop('disabled', true); - $('#msgSubmit').text(''); function postContact(token) { - var message = { - Name: $('#name').val(), - Email: $('#email').val(), + var payload = { + Name: name, + Email: email, Subject: '[MyAi.ro contact request]', - Message: $('#message').val(), + Message: message, CaptchaToken: token || '' }; $.ajax({ type: 'POST', url: '/api/contact', - data: JSON.stringify(message), + data: JSON.stringify(payload), contentType: 'application/json; charset=utf-8', dataType: 'json' }).done(function (resp) { @@ -468,18 +510,15 @@ var $msg = $('#matcherMsg'), $button = $('#matchSubmit'), $result = $('#matchResult'); - if (!file) { - $msg.removeClass().addClass('form-message text-danger').text(t('cv.noFile')); - return; - } - if (!jobUrl && !jobDescription) { - $msg.removeClass().addClass('form-message text-danger').text(t('cv.noJob')); - return; - } - if (!consent) { - $msg.removeClass().addClass('form-message text-danger').text(t('cv.noConsent')); - return; - } + + clearFieldErrors(['cvFileError', 'cvJobError', 'cvConsentError']); + $msg.removeClass().addClass('form-message').text(''); + + var hasError = false; + if (!file) { showFieldError('cvFileError', t('cv.noFile')); hasError = true; } + if (!jobUrl && !jobDescription) { showFieldError('cvJobError', t('cv.noJob')); hasError = true; } + if (!consent) { showFieldError('cvConsentError', t('cv.noConsent')); hasError = true; } + if (hasError) return; var $cvLoader = $('#cvLoader'); $button.prop('disabled', true).text(t('cv.processing')); $msg.removeClass().addClass('form-message').text(t('cv.extracting')); @@ -563,25 +602,32 @@ var $msg = $('#subscribeMsg'); var $button = $('#subscribeSubmit'); var $loader = $('#contactLoader'); - var email = $('#subscribeEmail').val(); + var email = $('#subscribeEmail').val().trim(); var consent = $('#subscribeConsent').is(':checked'); function setMsg(severity, key) { $msg.removeClass().addClass('form-message text-' + severity).text(t(key)); } - if (!consent) { - setMsg('danger', 'subscribe.noConsent'); - return; - } + clearFieldErrors(['subscribeEmailError', 'subscribeConsentError']); + $msg.removeClass().addClass('form-message').text(''); + + var hasError = false; if (!email) { - setMsg('danger', 'form.failed'); - return; + showFieldError('subscribeEmailError', t('form.required.email')); + hasError = true; + } else if (!isValidEmail(email)) { + showFieldError('subscribeEmailError', t('form.required.emailInvalid')); + hasError = true; } + if (!consent) { + showFieldError('subscribeConsentError', t('subscribe.noConsent')); + hasError = true; + } + if (hasError) return; $loader.css('display', 'flex'); $button.prop('disabled', true); - $msg.removeClass().addClass('form-message').text(''); function postSubscribe(token) { $.ajax({