@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
189 lines (187 loc) • 7.89 kB
JavaScript
/*
* 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;