Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

formik/ENG-4834 implement add/edit forms with formik #1565

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Loading