UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

189 lines (187 loc) 7.89 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import React, { useEffect, useMemo } from 'react'; import { isBlank } from '../../utils/string'; import Typography from '@mui/material/Typography'; import CheckCircleOutlineRoundedIcon from '@mui/icons-material/CheckCircleOutlineRounded'; import ErrorOutlineRoundedIcon from '@mui/icons-material/ErrorOutlineRounded'; import { passwordRequirementMessages } from '../../env/i18n-legacy'; import palette from '../../styles/palette'; import { makeStyles } from 'tss-react/mui'; const useStyles = makeStyles()( (theme, { listOfConditions, conditionItem, conditionItemIcon, conditionItemNotMet, conditionItemMet } = {}) => ({ listOfConditions: Object.assign({ listStyle: 'none', padding: 0, margin: '16px 0 16px 0' }, listOfConditions), conditionItem: Object.assign({ display: 'flex', alignItems: 'center' }, conditionItem), conditionItemIcon: Object.assign({ marginRight: theme.spacing(1) }, conditionItemIcon), conditionItemNotMet: Object.assign({ color: palette.yellow.shade }, conditionItemNotMet), conditionItemMet: Object.assign({ color: palette.green.shade }, conditionItemMet) }) ); export function PasswordRequirementsDisplay(props) { const { classes, cx } = useStyles(props.styles); const { passwordRequirementsRegex, formatMessage, value, onValidStateChanged } = props; const { regEx, conditions } = useMemo( () => getPrimeMatter({ passwordRequirementsRegex, formatMessage }), [passwordRequirementsRegex, formatMessage] ); useEffect(() => { onValidStateChanged(isBlank(value) ? null : regEx.test(value)); }, [onValidStateChanged, regEx, value]); return React.createElement( 'ul', { className: classes.listOfConditions }, conditions.map(({ description, regEx: condition }, key) => { const blank = isBlank(value); const valid = condition.test(value); return React.createElement( Typography, { key: key, component: 'li', className: cx( classes.conditionItem, !blank && { [classes.conditionItemNotMet]: !valid, [classes.conditionItemMet]: valid } ) }, valid && !blank ? React.createElement(CheckCircleOutlineRoundedIcon, { className: classes.conditionItemIcon }) : React.createElement(ErrorOutlineRoundedIcon, { className: classes.conditionItemIcon }), description ); }) ); } function getPrimeMatter(props) { const { passwordRequirementsRegex, formatMessage } = props; let regEx = null; let captureGroups = passwordRequirementsRegex.match(/\(\?<.*?>.*?\)/g); let namedCaptureGroupSupport = true; let fallback; if (!captureGroups) { // RegExp may be valid and have no capture groups fallback = { regEx, description: formatMessage(passwordRequirementMessages.validationPassing) }; } try { regEx = new RegExp(passwordRequirementsRegex); captureGroups = passwordRequirementsRegex.match(/\(\?<.*?>.*?\)/g); } catch (error) { console.warn(error); try { // reg ex without the capture groups and just need to remove the capture // If the reg ex is parsable without the capture groups, we can use the // group from the individual pieces later on the mapping. namedCaptureGroupSupport = false; regEx = new RegExp(passwordRequirementsRegex.replace(/\?<(.*?)>/g, '')); } catch (error) { // Allow everything and default to backend as regex wasn't // parsable/valid for current navigator regEx = /(.|\s)*\S(.|\s)*/; fallback = { regEx, description: formatMessage(passwordRequirementMessages.notBlank) }; console.warn('Defaulting password validation to server due to issues in RegExp compilation.'); } } return { regEx, conditions: captureGroups ? captureGroups.map((captureGroup) => { var _a, _b, _c; let description; let captureGroupKey = (_b = (_a = captureGroup.match(/\?<(.*?)>/g)) === null || _a === void 0 ? void 0 : _a[0].replace(/\?<|>/g, '')) !== null && _b !== void 0 ? _b : 'Unnamed condition'; if (!namedCaptureGroupSupport) { captureGroup = captureGroup.replace(/\?<(.*?)>/g, ''); } switch (captureGroupKey) { case 'hasSpecialChars': const allowedChars = (passwordRequirementsRegex.match(/\(\?<hasSpecialChars>(.*)\[(.*?)]\)/) || [ '', '', '' ])[2]; description = formatMessage(passwordRequirementMessages.hasSpecialChars, { chars: allowedChars ? `(${allowedChars})` : '' }); break; case 'minLength': const min = ((passwordRequirementsRegex.match(/\(\?<minLength>(.*){(.*?)}\)/) || [''])[0].match( /{(.*?)}/ ) || ['', ''])[1].split(',')[0]; description = formatMessage(passwordRequirementMessages.minLength, { min }); break; case 'maxLength': const max = ((passwordRequirementsRegex.match(/\(\?<maxLength>(.*){(.*?)}\)/) || [''])[0].match( /{(.*?)}/ ) || ['', ''])[1].split(',')[1]; description = formatMessage(passwordRequirementMessages.maxLength, { max }); break; case 'minMaxLength': const minLength = ((passwordRequirementsRegex.match(/\(\?<minMaxLength>(.*){(.*?)}\)/) || [''])[0].match( /{(.*?)}/ ) || ['', ''])[1].split(',')[0]; const maxLength = ((passwordRequirementsRegex.match(/\(\?<minMaxLength>(.*){(.*?)}\)/) || [''])[0].match( /{(.*?)}/ ) || ['', ''])[1].split(',')[1]; description = formatMessage(passwordRequirementMessages.minMaxLength, { minLength, maxLength }); break; default: description = formatMessage( (_c = passwordRequirementMessages[captureGroupKey]) !== null && _c !== void 0 ? _c : passwordRequirementMessages.unnamedGroup ); break; } return { regEx: new RegExp(captureGroup), description }; }) : [fallback] }; } export default PasswordRequirementsDisplay;