Files
myAi/web/wwwroot/js/utils/form-helpers.js
T
claude 1e8758796e Fix hardcoded user-facing strings — localize email fallbacks, API errors, AI parse messages
- Frontend: update extractApiError to check body.code first via i18n 'error.<code>' keys;
  add en/ro translations for cv_file_missing, captcha_verification_failed, request_cancelled
- email-data migration: seed 6 fallback template keys (match N/A, subject label, unknown IP,
  job search results empty states for keywords/providers/location)
- EmailApiEmailSender: replace "N/A", "Job", "Unknown" literals with template lookups
- CvSearchEmailSender: replace "none detected", "none", "-" literals with template lookups
- cv-matcher-data migration: seed parse-error.summary and parse-error.recommendation in AiPrompts
- CvMatcherService: look up localized parse-error messages from AiPrompts before calling ParseResult

Closes #53

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:32:03 +03:00

88 lines
3.1 KiB
JavaScript

/**
* Form Validation & Error Handling Utilities
*
* Shared helpers for form field validation and error display across all pages.
* Provides reusable patterns for: error messages, field validation, API error extraction.
*/
/**
* Display a field error message and mark its container with is-invalid class.
* Finds the appropriate parent container (label, form group, consent row, etc.)
* and toggles the is-invalid class based on whether msg is provided.
*
* @param {string} errorId - HTML ID of the error message element
* @param {string} [msg] - Error message to display; empty/null to clear
*/
function showFieldError(errorId, msg) {
var $el = $('#' + errorId);
$el.text(msg || '');
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);
}
/**
* Clear multiple field errors at once.
* Calls showFieldError(id, '') for each error element ID.
*
* @param {string[]} errorIds - Array of HTML IDs to clear
*/
function clearFieldErrors(errorIds) {
for (var i = 0; i < errorIds.length; i++) {
showFieldError(errorIds[i], '');
}
}
/**
* Validate email format using a simple regex pattern.
* Checks for: non-whitespace + @ + non-whitespace + . + non-whitespace
*
* @param {string} value - Email address to validate
* @returns {boolean} - True if email format is valid
*/
function isValidEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value || '').trim());
}
/**
* Extract user-facing error message from API response.
*
* Rules:
* - 429 (rate limit) → return rateLimitKey translation
* - 4xx with known error code → look up 'error.<code>' in i18n dictionary first
* - 4xx with error body → return server's error message (intentional feedback)
* - 5xx or no body → return fallbackKey translation
*
* @param {object|null} body - Parsed JSON response body
* @param {number} status - HTTP status code
* @param {string} fallbackKey - i18n key for 5xx/unknown errors (e.g., 'form.failed')
* @param {string} [rateLimitKey] - Optional i18n key for 429 (defaults to 'form.rateLimited')
* @returns {string} - User-facing error message
*/
function extractApiError(body, status, fallbackKey, rateLimitKey) {
if (status === 429) {
return window.MyAi.t(rateLimitKey || 'form.rateLimited');
}
if (status >= 400 && status < 500) {
// Prefer i18n translation keyed on the machine-readable error code
if (body && body.code) {
var codeKey = 'error.' + body.code;
var translated = window.MyAi.t(codeKey);
if (translated !== codeKey) return translated;
}
var msg = body && (body.error || body.Error || body.title);
if (msg) return msg;
}
return window.MyAi.t(fallbackKey);
}
// Expose helpers on window.MyAi for use by other scripts
window.MyAi = window.MyAi || {};
window.MyAi.showFieldError = showFieldError;
window.MyAi.clearFieldErrors = clearFieldErrors;
window.MyAi.isValidEmail = isValidEmail;
window.MyAi.extractApiError = extractApiError;