diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js index 76a11a24e5..643bbf2846 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/index.js @@ -304,6 +304,14 @@ const EditViewDataManagerProvider = ({ console.error(err); errors = getYupInnerErrors(err); + + toggleNotification({ + type: 'warning', + message: { + id: getTrad('containers.EditView.notification.errors'), + defaultMessage: 'The form contains some errors', + }, + }); } dispatch({ @@ -311,7 +319,16 @@ const EditViewDataManagerProvider = ({ errors, }); }, - [createFormData, isCreatingEntry, modifiedData, onPost, onPut, trackerProperty, yupSchema] + [ + createFormData, + isCreatingEntry, + modifiedData, + onPost, + onPut, + toggleNotification, + trackerProperty, + yupSchema, + ] ); const handlePublish = useCallback(async () => { diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js index c8295d0456..68fcea4734 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js @@ -295,6 +295,14 @@ const createYupSchemaAttribute = (type, validations, options) => { return value !== null; } + if (type === 'date' || type === 'datetime') { + if (typeof value === 'string') { + return !isEmpty(value); + } + + return !isEmpty(value.toString()); + } + return !isEmpty(value); }); } diff --git a/packages/core/admin/admin/src/content-manager/components/FieldComponent/Label.js b/packages/core/admin/admin/src/content-manager/components/FieldComponent/Label.js index 94fe3dc544..17bf7acc30 100644 --- a/packages/core/admin/admin/src/content-manager/components/FieldComponent/Label.js +++ b/packages/core/admin/admin/src/content-manager/components/FieldComponent/Label.js @@ -17,13 +17,15 @@ const Label = ({ intlLabel, id, labelAction, name, numberOfEntries, showNumberOf const label = intlLabel?.id ? formatMessage(intlLabel) : ''; return ( - - - {label} - {showNumberOfEntries && <> ({numberOfEntries})>} - - {labelAction && {labelAction}} - + + + + {label} + {showNumberOfEntries && <> ({numberOfEntries})>} + + {labelAction && {labelAction}} + + ); }; diff --git a/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js b/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js index ad3c81bcf6..d5045d7ae6 100644 --- a/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js +++ b/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js @@ -59,31 +59,32 @@ const FieldComponent = ({ return ( - - - {intlLabel && ( - - )} + + {intlLabel && ( + + )} - {showResetComponent && ( - } - onClick={() => { - removeComponentFromField(name, componentUid); - }} - /> - )} - + {showResetComponent && ( + } + noBorder + onClick={() => { + removeComponentFromField(name, componentUid); + }} + /> + )} + + {!isRepeatable && !isInitialized && ( )} diff --git a/packages/core/admin/admin/src/content-manager/components/Inputs/ComingSoonInput.js b/packages/core/admin/admin/src/content-manager/components/Inputs/ComingSoonInput.js index fe3ac764e2..9b6bd9e1ec 100644 --- a/packages/core/admin/admin/src/content-manager/components/Inputs/ComingSoonInput.js +++ b/packages/core/admin/admin/src/content-manager/components/Inputs/ComingSoonInput.js @@ -18,7 +18,7 @@ const CominSoonInput = ({ description, intlLabel, labelAction, error, name }) => ) : name; - const hint = description + const hint = description?.id ? formatMessage( { id: description.id, defaultMessage: description.defaultMessage }, { ...description.values } diff --git a/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js b/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js index 242c5adbd8..2b17af5af3 100644 --- a/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js +++ b/packages/core/admin/admin/src/content-manager/components/NonRepeatableComponent/index.js @@ -10,7 +10,7 @@ import { useContentTypeLayout } from '../../hooks'; import FieldComponent from '../FieldComponent'; import Inputs from '../Inputs'; -const NonRepeatableComponent = ({ componentUid, name }) => { +const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, name }) => { const { getComponentLayout } = useContentTypeLayout(); const componentLayoutData = useMemo(() => getComponentLayout(componentUid), [ componentUid, @@ -19,7 +19,15 @@ const NonRepeatableComponent = ({ componentUid, name }) => { const fields = componentLayoutData.layouts.edit; return ( - + {fields.map((fieldRow, key) => { return ( @@ -39,6 +47,7 @@ const NonRepeatableComponent = ({ componentUid, name }) => { id: metadatas.label, defaultMessage: metadatas.label, }} + isNested isRepeatable={fieldSchema.repeatable} max={fieldSchema.max} min={fieldSchema.min} @@ -67,8 +76,15 @@ const NonRepeatableComponent = ({ componentUid, name }) => { ); }; +NonRepeatableComponent.defaultProps = { + isFromDynamicZone: false, + isNested: false, +}; + NonRepeatableComponent.propTypes = { componentUid: PropTypes.string.isRequired, + isFromDynamicZone: PropTypes.bool, + isNested: PropTypes.bool, name: PropTypes.string.isRequired, }; diff --git a/packages/core/admin/admin/src/content-manager/components/SelectWrapper/index.js b/packages/core/admin/admin/src/content-manager/components/SelectWrapper/index.js index 028175ff3e..6eb420e575 100644 --- a/packages/core/admin/admin/src/content-manager/components/SelectWrapper/index.js +++ b/packages/core/admin/admin/src/content-manager/components/SelectWrapper/index.js @@ -1,27 +1,37 @@ import React, { useCallback, useState, useEffect, useMemo, memo } from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage, useIntl } from 'react-intl'; -import { Link, useLocation } from 'react-router-dom'; -import { findIndex, get, isArray, isEmpty, set } from 'lodash'; import { - DropdownIndicator, - LabelIconWrapper, + // FormattedMessage, + useIntl, +} from 'react-intl'; +// import { Link, useLocation } from 'react-router-dom'; +// import { findIndex, get, isArray, isEmpty, set } from 'lodash'; +import get from 'lodash/get'; +import isArray from 'lodash/isArray'; +import { + // DropdownIndicator, + NotAllowedInput, useCMEditViewDataManager, - useQueryParams, + // useQueryParams, } from '@strapi/helper-plugin'; -import { Flex, Text, Padded } from '@buffetjs/core'; -import { stringify } from 'qs'; +// import { Flex, Text, Padded } from '@buffetjs/core'; +// import { stringify } from 'qs'; import axios from 'axios'; import { axiosInstance } from '../../../core/utils'; -import { getTrad } from '../../utils'; -import SelectOne from '../SelectOne'; -import SelectMany from '../SelectMany'; -import ClearIndicator from './ClearIndicator'; -import IndicatorSeparator from './IndicatorSeparator'; -import Option from './Option'; -import { A, BaselineAlignment } from './components'; -import { connect, select, styles } from './utils'; +// import { getTrad } from '../../utils'; +import ComingSoonInput from '../Inputs/ComingSoonInput'; +// import SelectOne from '../SelectOne'; +// import SelectMany from '../SelectMany'; +// import ClearIndicator from './ClearIndicator'; +// import IndicatorSeparator from './IndicatorSeparator'; +// import Option from './Option'; +// import { A, BaselineAlignment } from './components'; +import { + connect, + select, + // styles +} from './utils'; const initialPaginationState = { _contains: '', @@ -29,77 +39,89 @@ const initialPaginationState = { _start: 0, }; -const buildParams = (query, paramsToKeep) => { - if (!paramsToKeep) { - return {}; - } +// const buildParams = (query, paramsToKeep) => { +// if (!paramsToKeep) { +// return {}; +// } - return paramsToKeep.reduce((acc, current) => { - const value = get(query, current, null); +// return paramsToKeep.reduce((acc, current) => { +// const value = get(query, current, null); - if (value) { - set(acc, current, value); - } +// if (value) { +// set(acc, current, value); +// } - return acc; - }, {}); -}; +// return acc; +// }, {}); +// }; function SelectWrapper({ description, - editable, - label, - labelIcon, + // editable, + labelAction, + intlLabel, isCreatingEntry, isFieldAllowed, isFieldReadable, mainField, name, relationType, - targetModel, - placeholder, + // targetModel, + // placeholder, queryInfos, }) { const { formatMessage } = useIntl(); - const [{ query }] = useQueryParams(); + // const [{ query }] = useQueryParams(); // Disable the input in case of a polymorphic relation const isMorph = useMemo(() => relationType.toLowerCase().includes('morph'), [relationType]); const { - addRelation, + // addRelation, modifiedData, - moveRelation, - onChange, - onRemoveRelation, + // moveRelation, + // onChange, + // onRemoveRelation, } = useCMEditViewDataManager(); - const { pathname } = useLocation(); + // const { pathname } = useLocation(); const value = get(modifiedData, name, null); - const [state, setState] = useState(initialPaginationState); - const [options, setOptions] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [isOpen, setIsOpen] = useState(false); + const [ + state, + // setState + ] = useState(initialPaginationState); + const [ + // options, + setOptions, + ] = useState([]); + const [ + // isLoading, + setIsLoading, + ] = useState(false); + const [ + isOpen, + // setIsOpen + ] = useState(false); - const filteredOptions = useMemo(() => { - return options.filter(option => { - if (!isEmpty(value)) { - // SelectMany - if (Array.isArray(value)) { - return findIndex(value, o => o.id === option.value.id) === -1; - } + // const filteredOptions = useMemo(() => { + // return options.filter(option => { + // if (!isEmpty(value)) { + // // SelectMany + // if (Array.isArray(value)) { + // return findIndex(value, o => o.id === option.value.id) === -1; + // } - // SelectOne - return get(value, 'id', '') !== option.value.id; - } + // // SelectOne + // return get(value, 'id', '') !== option.value.id; + // } - return true; - }); - }, [options, value]); + // return true; + // }); + // }, [options, value]); const { endPoint, containsKey, defaultParams, - shouldDisplayRelationLink, - paramsToKeep, + // shouldDisplayRelationLink, + // paramsToKeep, } = queryInfos; const isSingle = ['oneWay', 'oneToOne', 'manyToOne', 'oneToManyMorph', 'oneToOneMorph'].includes( @@ -170,15 +192,17 @@ function SelectWrapper({ } }, [ - isMorph, - isFieldAllowed, - state._limit, - state._contains, - defaultParams, containsKey, + defaultParams, endPoint, idsToOmit, + isFieldAllowed, + isMorph, mainField.name, + setIsLoading, + setOptions, + state._contains, + state._limit, ] ); @@ -193,180 +217,198 @@ function SelectWrapper({ return () => source.cancel('Operation canceled by the user.'); }, [getData, isOpen]); - const handleInputChange = (inputValue, { action }) => { - if (action === 'input-change') { - setState(prevState => { - if (prevState._contains === inputValue) { - return prevState; - } + // const handleInputChange = (inputValue, { action }) => { + // if (action === 'input-change') { + // setState(prevState => { + // if (prevState._contains === inputValue) { + // return prevState; + // } - return { ...prevState, _contains: inputValue, _start: 0 }; - }); - } + // return { ...prevState, _contains: inputValue, _start: 0 }; + // }); + // } - return inputValue; - }; + // return inputValue; + // }; - const handleMenuScrollToBottom = () => { - setState(prevState => ({ ...prevState, _limit: prevState._limit + 20 })); - }; + // const handleMenuScrollToBottom = () => { + // setState(prevState => ({ ...prevState, _limit: prevState._limit + 20 })); + // }; - const handleMenuClose = () => { - setState(initialPaginationState); - setIsOpen(false); - }; + // const handleMenuClose = () => { + // setState(initialPaginationState); + // setIsOpen(false); + // }; - const handleChange = value => { - onChange({ target: { name, value: value ? value.value : value } }); - }; + // const handleChange = value => { + // onChange({ target: { name, value: value ? value.value : value } }); + // }; - const handleAddRelation = value => { - if (!isEmpty(value)) { - addRelation({ target: { name, value } }); - } - }; + // const handleAddRelation = value => { + // if (!isEmpty(value)) { + // addRelation({ target: { name, value } }); + // } + // }; - const handleMenuOpen = () => { - setIsOpen(true); - }; + // const handleMenuOpen = () => { + // setIsOpen(true); + // }; - const to = `/content-manager/collectionType/${targetModel}/${value ? value.id : null}`; + // const to = `/content-manager/collectionType/${targetModel}/${value ? value.id : null}`; - const searchToPersist = stringify(buildParams(query, paramsToKeep), { encode: false }); + // const searchToPersist = stringify(buildParams(query, paramsToKeep), { encode: false }); - const link = useMemo(() => { - if (!value) { - return null; - } + // const link = useMemo(() => { + // if (!value) { + // return null; + // } - if (!shouldDisplayRelationLink) { - return null; - } + // if (!shouldDisplayRelationLink) { + // return null; + // } - return ( - - - {msg => {msg}} - - - ); - }, [shouldDisplayRelationLink, pathname, to, value, searchToPersist]); + // return ( + // + // + // {msg => {msg}} + // + // + // ); + // }, [shouldDisplayRelationLink, pathname, to, value, searchToPersist]); - const Component = isSingle ? SelectOne : SelectMany; + // const Component = isSingle ? SelectOne : SelectMany; const associationsLength = isArray(value) ? value.length : 0; - const isDisabled = useMemo(() => { - if (isMorph) { - return true; - } + // const isDisabled = useMemo(() => { + // if (isMorph) { + // return true; + // } - if (!isCreatingEntry) { - return (!isFieldAllowed && isFieldReadable) || !editable; - } + // if (!isCreatingEntry) { + // return (!isFieldAllowed && isFieldReadable) || !editable; + // } - return !editable; - }, [isMorph, isCreatingEntry, editable, isFieldAllowed, isFieldReadable]); + // return !editable; + // }, [isMorph, isCreatingEntry, editable, isFieldAllowed, isFieldReadable]); - const labelIconformatted = labelIcon - ? { icon: labelIcon.icon, title: formatMessage(labelIcon.title) } - : labelIcon; + const multipleLabel = intlLabel.id + ? formatMessage({ id: intlLabel.id, defaultMessage: intlLabel.defaultMessage }) + : name; + const formattedLabel = isSingle + ? intlLabel + : { + // Custom trad id in order to add the label count + id: 'relations-label', + defaultMessage: '{label} ({count})', + values: { label: multipleLabel, count: associationsLength }, + }; if (!isFieldAllowed && isCreatingEntry) { - return ; + return ; } if (!isCreatingEntry && !isFieldAllowed && !isFieldReadable) { - return ; + return ; } return ( - - - - - - - {label} - {!isSingle && ` (${associationsLength})`} - - - {labelIconformatted && ( - - - {labelIconformatted.icon} - - - )} - - {isSingle && link} - - {!isEmpty(description) && ( - - - - {description} - - - )} - - - - - ) : ( - placeholder - ) - } - searchToPersist={searchToPersist} - styles={styles} - targetModel={targetModel} - value={value} - /> - - - + ); + + // return ( + // + // + // + // + // + // + // {label} + // {!isSingle && ` (${associationsLength})`} + // + // + // {labelIconformatted && ( + // + // + // {labelIconformatted.icon} + // + // + // )} + // + // {isSingle && link} + // + // {!isEmpty(description) && ( + // + // + // + // {description} + // + // + // )} + // + // + + // + // ) : ( + // placeholder + // ) + // } + // searchToPersist={searchToPersist} + // styles={styles} + // targetModel={targetModel} + // value={value} + // /> + // + // + // + // ); } SelectWrapper.defaultProps = { - editable: true, + // editable: true, description: '', - label: '', - labelIcon: null, + labelAction: null, isFieldAllowed: true, - placeholder: '', + // placeholder: null, }; SelectWrapper.propTypes = { - editable: PropTypes.bool, - description: PropTypes.string, - label: PropTypes.string, - labelIcon: PropTypes.shape({ - icon: PropTypes.node.isRequired, - title: PropTypes.shape({ - id: PropTypes.string.isRequired, - defaultMessage: PropTypes.string, - }), + // editable: PropTypes.bool, + description: PropTypes.shape({ + id: PropTypes.string.isRequired, + defaultMessage: PropTypes.string.isRequired, + values: PropTypes.object, }), + intlLabel: PropTypes.shape({ + id: PropTypes.string.isRequired, + defaultMessage: PropTypes.string.isRequired, + values: PropTypes.object, + }).isRequired, + labelAction: PropTypes.element, isCreatingEntry: PropTypes.bool.isRequired, isFieldAllowed: PropTypes.bool, isFieldReadable: PropTypes.bool.isRequired, @@ -377,9 +419,13 @@ SelectWrapper.propTypes = { }).isRequired, }).isRequired, name: PropTypes.string.isRequired, - placeholder: PropTypes.string, + // placeholder: PropTypes.shape({ + // id: PropTypes.string.isRequired, + // defaultMessage: PropTypes.string.isRequired, + // values: PropTypes.object, + // }), relationType: PropTypes.string.isRequired, - targetModel: PropTypes.string.isRequired, + // targetModel: PropTypes.string.isRequired, queryInfos: PropTypes.shape({ containsKey: PropTypes.string.isRequired, defaultParams: PropTypes.object, diff --git a/packages/core/admin/admin/src/content-manager/pages/EditView/index.js b/packages/core/admin/admin/src/content-manager/pages/EditView/index.js index f5dcf8b942..c586157010 100644 --- a/packages/core/admin/admin/src/content-manager/pages/EditView/index.js +++ b/packages/core/admin/admin/src/content-manager/pages/EditView/index.js @@ -5,10 +5,12 @@ import { CheckPermissions, useTracking } from '@strapi/helper-plugin'; import { useIntl } from 'react-intl'; import { ContentLayout } from '@strapi/parts/Layout'; import { Box } from '@strapi/parts/Box'; +import { Divider } from '@strapi/parts/Divider'; import { Grid, GridItem } from '@strapi/parts/Grid'; import { LinkButton } from '@strapi/parts/LinkButton'; import { Main } from '@strapi/parts/Main'; import { Stack } from '@strapi/parts/Stack'; +import { TableLabel } from '@strapi/parts/Text'; import ConfigureIcon from '@strapi/icons/ConfigureIcon'; import EditIcon from '@strapi/icons/EditIcon'; import { InjectionZone } from '../../../shared/components'; @@ -18,7 +20,7 @@ import DynamicZone from '../../components/DynamicZone'; // import FormWrapper from '../../components/FormWrapper'; import FieldComponent from '../../components/FieldComponent'; import Inputs from '../../components/Inputs'; -// import SelectWrapper from '../../components/SelectWrapper'; +import SelectWrapper from '../../components/SelectWrapper'; import CollectionTypeFormWrapper from '../../components/CollectionTypeFormWrapper'; import EditViewDataManagerProvider from '../../components/EditViewDataManagerProvider'; import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper'; @@ -88,6 +90,9 @@ const EditView = ({ ); }, [currentContentTypeLayoutData]); + const relationsLayout = currentContentTypeLayoutData.layouts.editRelations; + const displayedRelationsLength = relationsLayout.length; + return ( {({ @@ -241,6 +246,66 @@ const EditView = ({ + {displayedRelationsLength > 0 && ( + + + {formatMessage( + { + id: getTrad('containers.Edit.relations'), + defaultMessage: + '{number, plural, =0 {relations} one {relation} other {relations}}', + }, + { number: displayedRelationsLength } + )} + + + + + + {relationsLayout.map( + ({ name, fieldSchema, labelAction, metadatas, queryInfos }) => { + return ( + + ); + } + )} + + + )} {slug !== 'strapi::administrator' && ( @@ -423,13 +488,13 @@ const EditView = ({ // ({ name, fieldSchema, labelIcon, metadatas, queryInfos }) => { // return ( // // ); // } diff --git a/packages/plugins/i18n/admin/src/components/CMEditViewInjectedComponents/CMEditViewLocalePicker/index.js b/packages/plugins/i18n/admin/src/components/CMEditViewInjectedComponents/CMEditViewLocalePicker/index.js index 5a5bd819d4..947e1e89c9 100644 --- a/packages/plugins/i18n/admin/src/components/CMEditViewInjectedComponents/CMEditViewLocalePicker/index.js +++ b/packages/plugins/i18n/admin/src/components/CMEditViewInjectedComponents/CMEditViewLocalePicker/index.js @@ -110,7 +110,6 @@ const CMEditViewLocalePicker = ({ : null} > {value.label} diff --git a/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js b/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js index 01dcba8156..defae91c07 100644 --- a/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js +++ b/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js @@ -1,8 +1,7 @@ import React from 'react'; import get from 'lodash/get'; import I18N from '@strapi/icons/I18N'; -// FIXME -import HelpIcon from '@strapi/icons/HelpIcon'; +import StrikedWorld from '@strapi/icons/StrikedWorld'; import LabelAction from '../components/LabelAction'; import { getTrad } from '../utils'; @@ -45,7 +44,7 @@ const enhanceEditLayout = layout => ? 'This value is unique for the selected locale' : 'This value is common to all locales', }, - icon: hasI18nEnabled ? : , + icon: hasI18nEnabled ? : , }; acc.push({ ...field, labelAction: }); diff --git a/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js b/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js index e45d70b8fd..19a401c91d 100644 --- a/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js +++ b/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js @@ -1,7 +1,7 @@ import React from 'react'; import I18N from '@strapi/icons/I18N'; -// FIXME -import HelpIcon from '@strapi/icons/HelpIcon'; + +import StrikedWorld from '@strapi/icons/StrikedWorld'; import LabelAction from '../../components/LabelAction'; import { getTrad } from '../../utils'; import mutateEditViewLayout, { @@ -450,7 +450,7 @@ describe('i18n | contentManagerHooks | mutateEditViewLayout', () => { labelAction: ( } + icon={} /> ), },