From 914b4b49f11d96c32cf8fa0b062fed2debae55fe Mon Sep 17 00:00:00 2001 From: Irakli Chalagashvili Date: Sun, 22 Oct 2023 15:46:25 +0400 Subject: [PATCH] ENG-4834 implement add/edit forms with formik --- src/locales/en.js | 1 + src/locales/it.js | 1 + src/locales/pt.js | 1 + src/state/component-repository/hub/actions.js | 7 +- .../components/list/AddNewRegistryModal.js | 86 ++++++++++++------- .../components/list/EditRegistryModal.js | 71 ++++++++------- 6 files changed, 105 insertions(+), 62 deletions(-) diff --git a/src/locales/en.js b/src/locales/en.js index 17105bba6..eb61184bf 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -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', diff --git a/src/locales/it.js b/src/locales/it.js index ef8e6e1af..dcb607391 100644 --- a/src/locales/it.js +++ b/src/locales/it.js @@ -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', diff --git a/src/locales/pt.js b/src/locales/pt.js index 9706abaee..3484a0635 100644 --- a/src/locales/pt.js +++ b/src/locales/pt.js @@ -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', diff --git a/src/state/component-repository/hub/actions.js b/src/state/component-repository/hub/actions.js index 4bf21b6a6..c47c257c9 100644 --- a/src/state/component-repository/hub/actions.js +++ b/src/state/component-repository/hub/actions.js @@ -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) { @@ -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) { @@ -269,6 +269,7 @@ export const sendPutRegistry = registryObject => dispatch => ( }); }).catch((err) => { dispatch(addToast(err.message || DEFAULT_BE_ERROR_MESSAGE, TOAST_ERROR)); + reject(); }); }) ); diff --git a/src/ui/component-repository/components/list/AddNewRegistryModal.js b/src/ui/component-repository/components/list/AddNewRegistryModal.js index e59b3a5c7..0c10db219 100644 --- a/src/ui/component-repository/components/list/AddNewRegistryModal.js +++ b/src/ui/component-repository/components/list/AddNewRegistryModal.js @@ -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) ? : undefined); -export const mustBeUnique = (values, key) => value => (value && values && values.includes(value) ? : 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 ; + } + 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 = [ , @@ -76,45 +84,63 @@ const AddNewRegistryModalForm = ({ closeLabel="app.cancel" modalCloseCleanup={handleCancel} > -
handleSave(values))}> +
} component={RenderTextInput} name="name" - type="text" validate={validateName} /> } component={RenderTextInput} name="url" - type="text" validate={validateUrl} /> } component={RenderTextInput} name="apiKey" - type="text" />
-
+ ); }; 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(), + url: Yup.string().required(), + apiKey: Yup.string(), + })), })(AddNewRegistryModalForm); export default AddNewRegistryModal; diff --git a/src/ui/component-repository/components/list/EditRegistryModal.js b/src/ui/component-repository/components/list/EditRegistryModal.js index efaa8eee0..7ceefc369 100644 --- a/src/ui/component-repository/components/list/EditRegistryModal.js +++ b/src/ui/component-repository/components/list/EditRegistryModal.js @@ -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(); @@ -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); @@ -63,7 +60,7 @@ const EditRegistryModalForm = ({ const handleCancel = () => { dispatch(setInfo({})); - dispatch(destroy(EditRegistryFormId)); + resetForm(); }; const buttons = [ @@ -71,8 +68,8 @@ const EditRegistryModalForm = ({ bsStyle="primary" type="submit" id="InstallationPlanModal__button-ok" - disabled={invalid || loading} - onClick={() => dispatch(submit(EditRegistryFormId))} + disabled={!isValid || loading} + onClick={() => handleSave(values)} > , @@ -91,7 +88,7 @@ const EditRegistryModalForm = ({ closeLabel="app.cancel" modalCloseCleanup={handleCancel} > -
handleSave(values))}> +
} @@ -114,7 +111,7 @@ const EditRegistryModalForm = ({ type="text" />
-
+ @@ -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(), + url: Yup.string().required(), + apiKey: Yup.string(), + })), })(EditRegistryModalForm); export default EditRegistryModal;