Skip to content

Commit

Permalink
Merge pull request #1570 from entando/ENG-4362_formik
Browse files Browse the repository at this point in the history
formik/ENG-4362 reimplement the my profile field validations with formik
  • Loading branch information
ichalagashvili authored Dec 6, 2023
2 parents e380a74 + 879f09e commit a34eb38
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 95 deletions.
4 changes: 2 additions & 2 deletions src/ui/common/attributes/AttributeInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { MODE_EDIT, MODE_ADD } from 'state/data-types/const';
import { convertReduxValidationsToFormikValidations } from 'helpers/formikUtils';


const maxLength10 = maxLength(10);
const maxLength15 = maxLength(15);
const maxLength50 = maxLength(50);

const AttributeInfo = ({ isSearchable, isIndexable, mode }) => {
Expand Down Expand Up @@ -68,7 +68,7 @@ const AttributeInfo = ({ isSearchable, isIndexable, mode }) => {
<FormLabel labelId="app.code" helpId="app.help.code" required />
}
validate={value =>
convertReduxValidationsToFormikValidations(value, [required, maxLength10])}
convertReduxValidationsToFormikValidations(value, [required, maxLength15])}
disabled={mode === MODE_EDIT}
/>
<Field
Expand Down
3 changes: 1 addition & 2 deletions src/ui/profile-types/add/AddFormContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { routeConverter } from '@entando/utils';
import { withRouter } from 'react-router-dom';

import { fetchProfileTypeAttributes, sendPostProfileType, setSelectedAttribute } from 'state/profile-types/actions';
import { getProfileTypeAttributesIdList, getAttributeTypeSelectFromProfileType } from 'state/profile-types/selectors';
import { getProfileTypeAttributesIdList } from 'state/profile-types/selectors';
import { setVisibleModal } from 'state/modal/actions';
import { ROUTE_PROFILE_TYPE_LIST } from 'app-init/router';
import { ConfirmCancelModalID } from 'ui/common/cancel-modal/ConfirmCancelModal';
Expand All @@ -13,7 +13,6 @@ import ProfileTypeForm from 'ui/profile-types/common/ProfileTypeForm';
export const mapStateToProps = state => ({
mode: 'add',
attributesType: getProfileTypeAttributesIdList(state),
attributeCode: getAttributeTypeSelectFromProfileType(state),
initialValues: {
code: '',
name: '',
Expand Down
20 changes: 12 additions & 8 deletions src/ui/profile-types/common/ProfileTypeForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export class ProfileTypeFormBody extends Component {
const {
attributesType, mode, handleSubmit,
onAddAttribute, isValid, isSubmitting,
profileTypeCode, intl,
dirty, onCancel, onDiscard, onSave, values,
profileTypeCode, values, intl,
isDirty, onCancel, onDiscard, onSave,
} = this.props;

const isEdit = mode === 'edit';

const handleCancelClick = () => {
if (dirty) {
if (isDirty) {
onCancel();
} else {
onDiscard();
Expand Down Expand Up @@ -183,26 +183,30 @@ ProfileTypeFormBody.propTypes = {
onAddAttribute: PropTypes.func,
attributesType: PropTypes.arrayOf(PropTypes.string).isRequired,
isValid: PropTypes.bool,
isSubmitting: PropTypes.bool.isRequired,
isSubmitting: PropTypes.bool,
mode: PropTypes.string,
profileTypeCode: PropTypes.string,
dirty: PropTypes.bool,
attributeCode: PropTypes.string,
isDirty: PropTypes.bool,
onSave: PropTypes.func.isRequired,
onDiscard: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
values: PropTypes.shape({
type: PropTypes.string,
type: PropTypes.shape({
code: PropTypes.string,
}),
}).isRequired,
};

ProfileTypeFormBody.defaultProps = {
onWillMount: () => {},
onAddAttribute: () => {},
isValid: false,
isSubmitting: false,
mode: 'add',
profileTypeCode: '',

dirty: false,
attributeCode: '',
isDirty: false,
};

const ProfileTypeForm = injectIntl(withFormik({
Expand Down
2 changes: 0 additions & 2 deletions src/ui/profile-types/edit/EditFormContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import {
getSelectedProfileTypeAttributes,
getProfileTypeAttributesIdList,
getAttributeTypeSelectFromProfileType,
getSelectedProfileType,
} from 'state/profile-types/selectors';
import { setVisibleModal, setInfo } from 'state/modal/actions';
Expand All @@ -33,7 +32,6 @@ export const mapStateToProps = (state, { match: { params } }) => (
profileTypeCode: params.profiletypeCode,
attributes: getSelectedProfileTypeAttributes(state),
attributesType: getProfileTypeAttributesIdList(state),
attributeCode: getAttributeTypeSelectFromProfileType(state),
routeToEdit: ROUTE_PROFILE_TYPE_ATTRIBUTE_EDIT,
initialValues: getSelectedProfileType(state),
}
Expand Down
80 changes: 44 additions & 36 deletions src/ui/user-profile/common/UserProfileField.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { Row, Col } from 'patternfly-react';
Expand Down Expand Up @@ -72,44 +72,48 @@ const getEnumeratorOptions = (component, items, separator, mandatory, intl) => {
}
};

const userProfileValidators = {};
export const generateValidatorFunc = (
value, validatorFuncName, validatorFunc,
validatorArray, parseValueFunc, customErrorId,
) => {
const userProfileValidators = {};
if (value === null || value === undefined) {
return;
}
const parsedValue = parseValueFunc ? parseValueFunc(value) : value;
if (!userProfileValidators[validatorFuncName]) {
userProfileValidators[validatorFuncName] = {};
}
if (!userProfileValidators[validatorFuncName][value]) {
userProfileValidators[validatorFuncName] = {
...userProfileValidators[validatorFuncName],
[value]: validatorFunc(parsedValue, customErrorId),
};
}
validatorArray.push(userProfileValidators[validatorFuncName][value]);
};

const UserProfileField = ({ attribute, intl, setFieldValue }) => {
export const UserProfileField = ({
attribute, intl, setFieldValue, disabled,
}) => {
const { validationRules } = attribute || {};
const {
minLength: textMinLen, maxLength: textMaxLen, regex, rangeEndNumber, rangeStartNumber,
} = validationRules || {};

const generateValidatorFunc = (
value, validatorFuncName, validatorFunc,
validatorArray, parseValueFunc, customErrorId,
) => {
if (value === null || value === undefined) {
return;
}
const parsedValue = parseValueFunc ? parseValueFunc(value) : value;
if (!userProfileValidators[validatorFuncName]) {
userProfileValidators[validatorFuncName] = {};
}
if (!userProfileValidators[validatorFuncName][value]) {
userProfileValidators[validatorFuncName] = {
...userProfileValidators[validatorFuncName],
[value]: validatorFunc(parsedValue, customErrorId),
};
}
validatorArray.push(userProfileValidators[validatorFuncName][value]);
};

const validateArray = [...(attribute.mandatory ? [required] : [])];
generateValidatorFunc(textMinLen, 'minLength', minLength, validateArray);
generateValidatorFunc(textMaxLen, 'maxLength', maxLength, validateArray);
generateValidatorFunc(
regex, 'regex', matchRegex, validateArray, RegexParser,
attribute.type === 'Email' && 'validateForm.email',
);
generateValidatorFunc(rangeEndNumber, 'rangeEndNumber', maxValue, validateArray);
generateValidatorFunc(rangeStartNumber, 'rangeStartNumber', minValue, validateArray);

const vArray = useMemo(() => {
const validateArray = [...(attribute.mandatory ? [required] : [])];
generateValidatorFunc(textMinLen, 'minLength', minLength, validateArray);
generateValidatorFunc(textMaxLen, 'maxLength', maxLength, validateArray);
generateValidatorFunc(
regex, 'regex', matchRegex, validateArray, RegexParser,
attribute.type === 'Email' && 'validateForm.email',
);
generateValidatorFunc(rangeEndNumber, 'rangeEndNumber', maxValue, validateArray);
generateValidatorFunc(rangeStartNumber, 'rangeStartNumber', minValue, validateArray);
return validateArray;
}, [attribute.mandatory, attribute.type, rangeEndNumber, rangeStartNumber,
regex, textMaxLen, textMinLen]);

const validationFunc = (value, validationFuncList) => {
let validation = null;
Expand Down Expand Up @@ -139,10 +143,11 @@ const UserProfileField = ({ attribute, intl, setFieldValue }) => {
helpText={getHelpMessage(attribute.validationRules, intl)}
required={attribute.mandatory}
/>}
validate={value => validationFunc(value, validateArray)}
validate={value => validationFunc(value, vArray)}
readOnly={readOnlyFields.includes(attribute.code)}
data-testid={`UserProfileForm__${attribute.code}Field`}
onPickDate={value => setFieldValue(attribute.code, value)}
disabled={disabled}
/>);
};

Expand All @@ -165,6 +170,11 @@ UserProfileField.propTypes = {
attribute: basicAttributeShape.isRequired,
intl: intlShape.isRequired,
setFieldValue: PropTypes.func.isRequired,
disabled: PropTypes.bool,
};

UserProfileField.defaultProps = {
disabled: false,
};

export const CompositeField = ({
Expand Down Expand Up @@ -232,5 +242,3 @@ CompositeField.defaultProps = {
fieldName: '',
noLabel: false,
};

export default UserProfileField;
2 changes: 1 addition & 1 deletion src/ui/user-profile/common/UserProfileForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import get from 'lodash/get';
import { Button, Row, Col, FormGroup } from 'patternfly-react';
import { required } from '@entando/utils';
import { FormattedMessage, defineMessages, injectIntl, intlShape } from 'react-intl';
import UserProfileField, { CompositeField } from 'ui/user-profile/common/UserProfileField';
import { UserProfileField, CompositeField } from 'ui/user-profile/common/UserProfileField';
import FormLabel from 'ui/common/form/FormLabel';
import RenderListField from 'ui/common/formik-field/RenderListField';
import { BOOLEAN_OPTIONS, THREE_STATE_OPTIONS, getTranslatedOptions } from 'ui/users/common/const';
Expand Down
63 changes: 19 additions & 44 deletions src/ui/users/my-profile/MyProfileEditForm.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import { withFormik, Field, FieldArray } from 'formik';
import { withFormik, FieldArray } from 'formik';
import { FormattedMessage, defineMessages, injectIntl, intlShape } from 'react-intl';
import { Panel } from 'react-bootstrap';
import { Form, Button, Row, Col } from 'patternfly-react';
Expand All @@ -15,9 +15,7 @@ import {
TYPE_BOOLEAN, TYPE_THREESTATE, TYPE_ENUMERATOR, TYPE_ENUMERATOR_MAP, TYPE_MONOLIST, TYPE_LIST,
TYPE_COMPOSITE,
} from 'state/data-types/const';
import { getComponentType } from 'helpers/formikEntities';

const defaultAttrCodes = ['fullname', 'email'];
import { UserProfileField } from 'ui/user-profile/common/UserProfileField';

const getComponentOptions = (component, intl) => {
const booleanOptions = getTranslatedOptions(intl, BOOLEAN_OPTIONS);
Expand Down Expand Up @@ -75,42 +73,6 @@ const getHelpMessage = (validationRules, intl) => {
return null;
};

const field = (intl, attribute, disabled) => {
const labelProp = defaultAttrCodes.includes(attribute.code) ? ({
labelId: `user.table.${attribute.code}`,
}) : ({
labelText: attribute.name,
});

return (
<Field
key={attribute.code}
component={getComponentType(attribute.type)}
name={attribute.code}
rows={3}
toggleElement={getComponentOptions(attribute.type, intl)}
options={getEnumeratorOptions(
attribute.type,
attribute.enumeratorStaticItems,
attribute.enumeratorStaticItemsSeparator,
attribute.mandatory,
intl,
)}
optionValue="value"
optionDisplayName="optionDisplayName"
label={<FormLabel
{...labelProp}
helpText={getHelpMessage(attribute.validationRules, intl)}
required={attribute.mandatory}
/>}
disabled={disabled}
/>
);
};

const renderCompositeAttribute = (intl, compositeAttributes, disabled) =>
compositeAttributes.map(attribute => field(intl, attribute, disabled));

class MyProfileEditFormBody extends Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -151,11 +113,23 @@ class MyProfileEditFormBody extends Component {
render() {
const {
profileTypesAttributes, defaultLanguage, languages, intl, userEmail, onChangeProfilePicture,
handleSubmit, setFieldValue, isValid, resetForm, values,
handleSubmit, setFieldValue, isValid, resetForm, values, isSubmitting,
} = this.props;

const { editMode } = this.state;

const field = (attribute, disabled) => (
<UserProfileField
key={attribute.code}
attribute={attribute}
intl={intl}
disabled={disabled}
/>
);

const renderCompositeAttribute = (compositeAttributes, disabled) =>
compositeAttributes.map(attribute => field(attribute, disabled));

const renderFieldArray = (attributeCode, attribute, component, language) => (<FieldArray
key={attributeCode}
component={component}
Expand Down Expand Up @@ -199,7 +173,7 @@ class MyProfileEditFormBody extends Component {
<Panel>
<Panel.Body>
<div name={attribute.code}>
{renderCompositeAttribute(intl, attribute.compositeAttributes, !editMode)}
{renderCompositeAttribute(attribute.compositeAttributes, !editMode)}
</div>
</Panel.Body>
</Panel>
Expand All @@ -223,7 +197,7 @@ class MyProfileEditFormBody extends Component {
</Row>
);
}
return field(intl, attribute, !editMode);
return field(attribute, !editMode);
});

const { profilepicture } = values;
Expand Down Expand Up @@ -252,7 +226,7 @@ class MyProfileEditFormBody extends Component {
className="pull-right"
type="submit"
bsStyle="primary"
disabled={!isValid}
disabled={!isValid || isSubmitting}
data-testid="profile_saveBtn"
onClick={() => {
this.changeMode();
Expand Down Expand Up @@ -332,6 +306,7 @@ MyProfileEditFormBody.propTypes = {
userEmail: PropTypes.string,
onChangeProfilePicture: PropTypes.func.isRequired,
isValid: PropTypes.bool.isRequired,
isSubmitting: PropTypes.bool.isRequired,
};

MyProfileEditFormBody.defaultProps = {
Expand Down

0 comments on commit a34eb38

Please sign in to comment.