Skip to content

Commit

Permalink
Merge pull request #1565 from entando/ENG-4834_correct_error
Browse files Browse the repository at this point in the history
formik/ENG-4834 implement add/edit forms with formik
  • Loading branch information
ichalagashvili authored Oct 23, 2023
2 parents af4f394 + 914b4b4 commit 111b85b
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 62 deletions.
1 change: 1 addition & 0 deletions src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,7 @@ export default {
'hub.newRegistry.apiKey': 'API Key',
'hub.newRegistry.name.error': 'Registry name must be unique',
'hub.newRegistry.url.error': 'Registry URL must be unique',
'hub.newRegistry.protocol.error': 'AppBuilder is currently running on a certified domain, hence it is only possible to add registries with an HTTPS url',
'hub.editRegistry.alert': 'Submitting this form will replace the existing API Key. If none will be submitted, the old one will be deleted.',
'app.filterTypesSelect.organizationName': 'Organization Name',
'app.filterTypesSelect.bundleGroup': 'Bundle Group',
Expand Down
1 change: 1 addition & 0 deletions src/locales/it.js
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,7 @@ export default {
'hub.newRegistry.apiKey': 'API Key',
'hub.newRegistry.name.error': 'Il nome del registro deve essere univoco',
'hub.newRegistry.url.error': 'L\'URL del registro deve essere univoco',
'hub.newRegistry.protocol.error': 'AppBuilder si trova in un dominio certificato ed è pertanto possibile aggiungere solo registry con un url di tipo HTTPS',
'hub.editRegistry.alert': 'Sottomettere questo modulo sostituirà l\'esistente API Key. Se non ne verrà inviata alcuna, quella precedente verrà eliminata.',
'app.filterTypesSelect.organizationName': 'Nome dell\'organizzazione',
'app.filterTypesSelect.bundleGroup': 'Gruppo pacchetto',
Expand Down
1 change: 1 addition & 0 deletions src/locales/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,7 @@ export default {
'hub.newRegistry.apiKey': 'API Key',
'hub.newRegistry.name.error': 'O nome do registro deve ser único',
'hub.newRegistry.url.error': 'O URL do registro deve ser exclusivo',
'hub.newRegistry.protocol.error': 'O AppBuilder está localizado em um domínio certificado e portanto é possível adicionar apenas registros com URL HTTPS',
'hub.editRegistry.alert': 'Ao enviar este formulário, a API Key existente será substituída. Se nenhuma for enviada, a antiga será excluída.',
'app.filterTypesSelect.organizationName': 'Nome da organização',
'app.filterTypesSelect.bundleGroup': 'Grupo de Pacotes',
Expand Down
7 changes: 4 additions & 3 deletions src/state/component-repository/hub/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export const sendDeleteRegistry = registryId => dispatch => (
);

export const sendPostRegistry = registryObject => dispatch => (
new Promise((resolve) => {
new Promise((resolve, reject) => {
addRegistry(registryObject).then((response) => {
response.json().then((data) => {
if (response.ok) {
Expand All @@ -243,13 +243,13 @@ export const sendPostRegistry = registryObject => dispatch => (
});
}).catch((err) => {
dispatch(addToast(err.message || DEFAULT_BE_ERROR_MESSAGE, TOAST_ERROR));
}).finally(() => {
reject();
});
})
);

export const sendPutRegistry = registryObject => dispatch => (
new Promise((resolve) => {
new Promise((resolve, reject) => {
updateRegistry(registryObject).then((response) => {
response.json().then((data) => {
if (response.ok) {
Expand All @@ -269,6 +269,7 @@ export const sendPutRegistry = registryObject => dispatch => (
});
}).catch((err) => {
dispatch(addToast(err.message || DEFAULT_BE_ERROR_MESSAGE, TOAST_ERROR));
reject();
});
})
);
Expand Down
86 changes: 56 additions & 30 deletions src/ui/component-repository/components/list/AddNewRegistryModal.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,71 @@
import React, { useMemo, useState } from 'react';
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { Button, Modal } from 'patternfly-react';
import { Field, reduxForm, destroy, submit, Form, reset } from 'redux-form';
import { Field, withFormik } from 'formik';
import PropTypes from 'prop-types';
import { required } from '@entando/utils';
import * as Yup from 'yup';

import GenericModalContainer from 'ui/common/modal/GenericModalContainer';
import RenderTextInput from 'ui/common/form/RenderTextInput';
import RenderTextInput from 'ui/common/formik-field/RenderTextInput';
import FormLabel from 'ui/common/form/FormLabel';
import { getRegistries } from 'state/component-repository/hub/selectors';
import { sendPostRegistry } from 'state/component-repository/hub/actions';
import { setVisibleModal } from 'state/modal/actions';
import { convertReduxValidationsToFormikValidations } from 'helpers/formikUtils';

export const ADD_NEW_REGISTRY_MODAL_ID = 'AddNewRegistryModal';

const NewRegistryFormId = 'NewRegistryFormId';
export const mustBeUnique = (values, value, key) => (value && values && values.includes(value) ? <FormattedMessage id={`hub.newRegistry.${key}.error`} /> : undefined);

export const mustBeUnique = (values, key) => value => (value && values && values.includes(value) ? <FormattedMessage id={`hub.newRegistry.${key}.error`} /> : undefined);
export const protocolCompliance = (value) => {
// get current protocol from window.location.protocol
const currentProtocol = window.location.protocol;
// if current protocol is https, then the protocol must be https
if (currentProtocol === 'https:' && value && value.startsWith('http://')) {
return <FormattedMessage id="hub.newRegistry.protocol.error" />;
}
return undefined;
};

const AddNewRegistryModalForm = ({
invalid, handleSubmit,
isValid, resetForm, values,
}) => {
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();

const existingRegistries = useSelector(getRegistries);

const names = useMemo(() => existingRegistries.map(reg => reg.name), [existingRegistries]);
const urls = useMemo(() => existingRegistries.map(reg => reg.url), [existingRegistries]);
const names = existingRegistries.map(reg => reg.name);
const urls = existingRegistries.map(reg => reg.url);

const validateName = useMemo(() => [required, mustBeUnique(names, 'name')], [names]);
const validateUrl = useMemo(() => [required, mustBeUnique(urls, 'url')], [urls]);
const validateName = value => convertReduxValidationsToFormikValidations(value, [currVal => mustBeUnique(names, currVal, 'name')]);
const validateUrl = value => convertReduxValidationsToFormikValidations(value, [currVal => mustBeUnique(urls, currVal, 'url'), protocolCompliance]);

const handleSave = (values) => {
const handleSave = (formValues) => {
setLoading(true);
dispatch(sendPostRegistry(values)).then((isSuccess) => {
dispatch(sendPostRegistry(formValues)).then((isSuccess) => {
setLoading(false);
resetForm();
if (isSuccess) {
dispatch(setVisibleModal(''));
dispatch(destroy(NewRegistryFormId));
} else {
reset(NewRegistryFormId);
}
}).catch(() => {
setLoading(false);
reset(NewRegistryFormId);
});
};

const handleCancel = () => dispatch(destroy(NewRegistryFormId));
const handleCancel = () => {
resetForm();
};

const buttons = [
<Button
bsStyle="primary"
type="submit"
id="InstallationPlanModal__button-ok"
disabled={invalid || loading}
onClick={() => dispatch(submit(NewRegistryFormId))}
disabled={!isValid || loading}
onClick={() => handleSave(values)}
>
<FormattedMessage id="app.save" />
</Button>,
Expand All @@ -76,45 +84,63 @@ const AddNewRegistryModalForm = ({
closeLabel="app.cancel"
modalCloseCleanup={handleCancel}
>
<Form className="form-horizontal" onSubmit={handleSubmit(values => handleSave(values))}>
<form className="form-horizontal">
<fieldset className="no-padding">
<Field
label={<FormLabel labelId="hub.newRegistry.name" required />}
component={RenderTextInput}
name="name"
type="text"
validate={validateName}
/>
<Field
label={<FormLabel labelId="hub.newRegistry.url" required />}
component={RenderTextInput}
name="url"
type="text"
validate={validateUrl}
/>
<Field
label={<FormLabel labelId="hub.newRegistry.apiKey" />}
component={RenderTextInput}
name="apiKey"
type="text"
/>
</fieldset>
</Form>
</form>
</GenericModalContainer>
);
};

AddNewRegistryModalForm.propTypes = {
invalid: PropTypes.bool,
handleSubmit: PropTypes.func.isRequired,
isValid: PropTypes.bool,
resetForm: PropTypes.func.isRequired,
values: PropTypes.shape({
name: PropTypes.string,
url: PropTypes.string,
apiKey: PropTypes.string,
}).isRequired,
};

AddNewRegistryModalForm.defaultProps = {
invalid: false,
isValid: false,
};

const AddNewRegistryModal = reduxForm({
form: NewRegistryFormId,
const AddNewRegistryModal = withFormik({
mapPropsToValues: () => {
const values = {
name: '',
url: '',
apiKey: '',
};
return values;
},
displayName: 'addNewRegistryModalForm',
validateOnMount: true,
enableReinitialize: true,
validationSchema: () => (
Yup.object().shape({
name: Yup.string().required(<FormattedMessage id="validateForm.required" />),
url: Yup.string().required(<FormattedMessage id="validateForm.required" />),
apiKey: Yup.string(),
})),
})(AddNewRegistryModalForm);

export default AddNewRegistryModal;
71 changes: 42 additions & 29 deletions src/ui/component-repository/components/list/EditRegistryModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,24 @@ import React, { useEffect, useMemo, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { Button, Modal, Alert } from 'patternfly-react';
import { Field, reduxForm, destroy, submit, Form, reset, initialize } from 'redux-form';
import { Field, withFormik } from 'formik';
import PropTypes from 'prop-types';
import { required } from '@entando/utils';
import * as Yup from 'yup';

import GenericModalContainer from 'ui/common/modal/GenericModalContainer';
import RenderTextInput from 'ui/common/form/RenderTextInput';
import RenderTextInput from 'ui/common/formik-field/RenderTextInput';
import FormLabel from 'ui/common/form/FormLabel';
import { getRegistries } from 'state/component-repository/hub/selectors';
import { sendPutRegistry } from 'state/component-repository/hub/actions';
import { setInfo, setVisibleModal } from 'state/modal/actions';
import { getInfo } from 'state/modal/selectors';
import { mustBeUnique } from 'ui/component-repository/components/list/AddNewRegistryModal';
import { mustBeUnique, protocolCompliance } from 'ui/component-repository/components/list/AddNewRegistryModal';
import { convertReduxValidationsToFormikValidations } from 'helpers/formikUtils';

export const EDIT_REGISTRY_MODAL_ID = 'EditRegistryModal';

const EditRegistryFormId = 'EditRegistryFormId';

const EditRegistryModalForm = ({
invalid, handleSubmit,
isValid, resetForm, values, setValues,
}) => {
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
Expand All @@ -33,28 +32,26 @@ const EditRegistryModalForm = ({
[existingRegistries, editData],
);

const names = useMemo(() => filteredRegistries.map(reg => reg.name), [filteredRegistries]);
const urls = useMemo(() => filteredRegistries.map(reg => reg.url), [filteredRegistries]);
const names = filteredRegistries.map(reg => reg.name);
const urls = filteredRegistries.map(reg => reg.url);

const validateName = useMemo(() => [required, mustBeUnique(names, 'name')], [names]);
const validateUrl = useMemo(() => [required, mustBeUnique(urls, 'url')], [urls]);
const validateName = value => convertReduxValidationsToFormikValidations(value, [currVal => mustBeUnique(names, currVal, 'name')]);
const validateUrl = value => convertReduxValidationsToFormikValidations(value, [currVal => mustBeUnique(urls, currVal, 'url'), protocolCompliance]);

useEffect(() => {
if (editData) {
dispatch((initialize(EditRegistryFormId, editData)));
setValues(editData);
}
}, [dispatch, editData]);
}, [editData, setValues]);

const handleSave = (values) => {
const handleSave = (newValues) => {
setLoading(true);
dispatch(sendPutRegistry(values)).then((isSuccess) => {
dispatch(sendPutRegistry(newValues)).then((isSuccess) => {
setLoading(false);
resetForm();
if (isSuccess) {
dispatch(setVisibleModal(''));
dispatch(setInfo({}));
dispatch(destroy(EditRegistryFormId));
} else {
reset(EditRegistryFormId);
}
}).catch(() => {
setLoading(false);
Expand All @@ -63,16 +60,16 @@ const EditRegistryModalForm = ({

const handleCancel = () => {
dispatch(setInfo({}));
dispatch(destroy(EditRegistryFormId));
resetForm();
};

const buttons = [
<Button
bsStyle="primary"
type="submit"
id="InstallationPlanModal__button-ok"
disabled={invalid || loading}
onClick={() => dispatch(submit(EditRegistryFormId))}
disabled={!isValid || loading}
onClick={() => handleSave(values)}
>
<FormattedMessage id="app.save" />
</Button>,
Expand All @@ -91,7 +88,7 @@ const EditRegistryModalForm = ({
closeLabel="app.cancel"
modalCloseCleanup={handleCancel}
>
<Form className="form-horizontal" onSubmit={handleSubmit(values => handleSave(values))}>
<form className="form-horizontal">
<fieldset className="no-padding">
<Field
label={<FormLabel labelId="hub.newRegistry.name" required />}
Expand All @@ -114,7 +111,7 @@ const EditRegistryModalForm = ({
type="text"
/>
</fieldset>
</Form>
</form>
<Alert type="info">
<FormattedMessage id="hub.editRegistry.alert" />
</Alert>
Expand All @@ -123,18 +120,34 @@ const EditRegistryModalForm = ({
};

EditRegistryModalForm.propTypes = {
invalid: PropTypes.bool,
handleSubmit: PropTypes.func.isRequired,
isValid: PropTypes.bool,
setValues: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired,
values: PropTypes.shape({
name: PropTypes.string,
url: PropTypes.string,
apiKey: PropTypes.string,
}).isRequired,
};

EditRegistryModalForm.defaultProps = {
invalid: false,
isValid: true,
};

const EditRegistryModal = reduxForm({
form: EditRegistryFormId,
const EditRegistryModal = withFormik({
enableReinitialize: true,
keepDirtyOnReinitialize: true,
validateOnMount: true,
mapPropsToValues: () => ({
name: '',
url: '',
apiKey: '',
}),
validationSchema: () => (
Yup.object().shape({
name: Yup.string().required(<FormattedMessage id="validateForm.required" />),
url: Yup.string().required(<FormattedMessage id="validateForm.required" />),
apiKey: Yup.string(),
})),
})(EditRegistryModalForm);

export default EditRegistryModal;

0 comments on commit 111b85b

Please sign in to comment.