UNPKG

mattermost-redux

Version:

Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client

185 lines (184 loc) 7.31 kB
"use strict"; // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. Object.defineProperty(exports, "__esModule", { value: true }); exports.checkDialogElementForError = checkDialogElementForError; exports.checkIfErrorsMatchElements = checkIfErrorsMatchElements; const date_fns_1 = require("date-fns"); const react_intl_1 = require("react-intl"); // Validation patterns for exact storage format matching const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/; // YYYY-MM-DD const DATETIME_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/; // YYYY-MM-DDTHH:mm:ssZ or with offset // Relative pattern: [+-]NNN[dwmHMS] const RELATIVE_PATTERN = /^([+-]\d{1,3})([dwmHMS])$/; /** * Resolves a min_date/max_date bound string to a Date. * Handles relative patterns (+2H, +30M, +7d, etc.) and ISO date/datetime strings. * Returns null if the value cannot be resolved. */ function resolveBoundToDate(value) { // Named relative words if (value === 'today') { return (0, date_fns_1.startOfDay)(new Date()); } if (value === 'tomorrow') { return (0, date_fns_1.startOfDay)((0, date_fns_1.addDays)(new Date(), 1)); } if (value === 'yesterday') { return (0, date_fns_1.startOfDay)((0, date_fns_1.addDays)(new Date(), -1)); } // Dynamic relative patterns: +2H, +30M, +7d, etc. const match = value.match(RELATIVE_PATTERN); if (match) { const amount = parseInt(match[1], 10); const unit = match[2]; const now = new Date(); switch (unit) { case 'd': return (0, date_fns_1.startOfDay)((0, date_fns_1.addDays)(now, amount)); case 'w': return (0, date_fns_1.startOfDay)((0, date_fns_1.addWeeks)(now, amount)); case 'm': return (0, date_fns_1.startOfDay)((0, date_fns_1.addMonths)(now, amount)); case 'H': return (0, date_fns_1.addHours)(now, amount); case 'M': return (0, date_fns_1.addMinutes)(now, amount); case 'S': return (0, date_fns_1.addSeconds)(now, amount); default: return null; } } const parsed = (0, date_fns_1.parseISO)(value); return (0, date_fns_1.isValid)(parsed) ? parsed : null; } /** * Validates date/datetime field values for format and range constraints */ function validateDateTimeValue(value, elem) { const parsedDate = (0, date_fns_1.parseISO)(value); if (!(0, date_fns_1.isValid)(parsedDate)) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.bad_format', defaultMessage: 'Invalid date format', }); } const isDateField = elem.type === 'date'; if (isDateField) { if (!DATE_FORMAT_PATTERN.test(value)) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.bad_date_format', defaultMessage: 'Date field must be in YYYY-MM-DD format', }); } } else if (!DATETIME_FORMAT_PATTERN.test(value)) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.bad_datetime_format', defaultMessage: 'DateTime field must be in YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:MM format', }); } // Range validation against min_date / max_date if (elem.min_date) { const minDate = resolveBoundToDate(elem.min_date); if (minDate && parsedDate < minDate) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.before_min_date', defaultMessage: 'Selected time is before the minimum allowed date.', }); } } if (elem.max_date) { const maxDate = resolveBoundToDate(elem.max_date); if (maxDate && parsedDate > maxDate) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.after_max_date', defaultMessage: 'Selected time is after the maximum allowed date.', }); } } return null; } function checkDialogElementForError(elem, value) { // Check if value is empty (handles arrays for multiselect) let isEmpty; if (value === 0) { isEmpty = false; } else if (Array.isArray(value)) { isEmpty = value.length === 0; } else { isEmpty = !value; } if (isEmpty && !elem.optional) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.required', defaultMessage: 'This field is required.', }); } const type = elem.type; if (type === 'text' || type === 'textarea') { if (value && value.length < elem.min_length) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.too_short', // minLength provided by InteractiveDialog // eslint-disable-next-line formatjs/enforce-placeholders defaultMessage: 'Minimum input length is {minLength}.', }); } if (elem.subtype === 'email') { if (value && !value.includes('@')) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.bad_email', defaultMessage: 'Must be a valid email address.', }); } } if (elem.subtype === 'number') { if (value && isNaN(value)) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.bad_number', defaultMessage: 'Must be a number.', }); } } if (elem.subtype === 'url') { if (value && !value.includes('http://') && !value.includes('https://')) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.bad_url', defaultMessage: 'URL must include http:// or https://.', }); } } } else if (type === 'radio') { const options = elem.options; if (typeof value !== 'undefined' && Array.isArray(options) && !options.some((e) => e.value === value)) { return (0, react_intl_1.defineMessage)({ id: 'interactive_dialog.error.invalid_option', defaultMessage: 'Must be a valid option', }); } } else if (type === 'date' || type === 'datetime') { // Validate date/datetime format and range constraints if (value && typeof value === 'string') { const validationError = validateDateTimeValue(value, elem); if (validationError) { return validationError; } } return null; } return null; } // If we're returned errors that don't match any of the elements we have, // ignore them and complete the dialog function checkIfErrorsMatchElements(errors = {}, elements = []) { for (const name in errors) { if (!Object.hasOwn(errors, name)) { continue; } for (const elem of elements) { if (elem.name === name) { return true; } } } return false; }