Fix CTM dz min max validations

This commit is contained in:
soupette 2019-12-11 16:52:35 +01:00
parent 4ad9fdef52
commit e29bd761e4
11 changed files with 94 additions and 25 deletions

View File

@ -43,6 +43,15 @@
"full_name": { "full_name": {
"type": "string", "type": "string",
"required": true "required": true
},
"dz": {
"type": "dynamiczone",
"components": [
"default.dish",
"default.openingtimes",
"default.restaurantservice"
],
"min": 2
} }
} }
} }

View File

@ -18,12 +18,6 @@
"price": { "price": {
"type": "float" "type": "float"
}, },
"address": {
"model": "address"
},
"addresses": {
"collection": "address"
},
"picture": { "picture": {
"model": "file", "model": "file",
"via": "related", "via": "related",

View File

@ -25,7 +25,7 @@ const ComponentWrapper = styled.div`
content: '&'; content: '&';
position: absolute; position: absolute;
top: -30px; top: -30px;
left: 22px; left: 24.5px;
height: 100%; height: 100%;
width: 2px; width: 2px;
background-color: #e6f0fb; background-color: #e6f0fb;

View File

@ -10,7 +10,7 @@ const Label = styled.div`
content: '•'; content: '•';
position: absolute; position: absolute;
top: 15px; top: 15px;
left: 21.25px; left: 21.5px;
font-size: 15px; font-size: 15px;
width: 8px; width: 8px;
height: 8px; height: 8px;

View File

@ -72,6 +72,10 @@ const DynamicZone = ({ max, min, name }) => {
get(dynamicZoneErrors, [0, 'id'], '').includes('min'); get(dynamicZoneErrors, [0, 'id'], '').includes('min');
const hasRequiredError = hasError && !hasMinError; const hasRequiredError = hasError && !hasMinError;
const hasMaxError =
hasError &&
get(dynamicZoneErrors, [0, 'id'], '') ===
'components.Input.error.validation.max';
return ( return (
<DynamicZoneWrapper> <DynamicZoneWrapper>
@ -144,9 +148,14 @@ const DynamicZone = ({ max, min, name }) => {
} }
}} }}
/> />
{hasRequiredError && !isOpen && ( {hasRequiredError && !isOpen && !hasMaxError && (
<div className="error-label">Component is required</div> <div className="error-label">Component is required</div>
)} )}
{hasMaxError && !isOpen && (
<div className="error-label">
<FormattedMessage id="components.Input.error.validation.max" />
</div>
)}
{hasMinError && !isOpen && ( {hasMinError && !isOpen && (
<div className="error-label"> <div className="error-label">
<FormattedMessage <FormattedMessage

View File

@ -128,7 +128,8 @@ const BannerWrapper = styled.button`
svg { svg {
font-size: 10px; font-size: 10px;
path { path {
fill: #292b2c; // fill: #292b2c;
fill: #4B515A;
} }
} }
} }
@ -139,14 +140,18 @@ const BannerWrapper = styled.button`
} }
${({ hasErrors, isOpen }) => { ${({ hasErrors, isOpen }) => {
let fill = '#B4B6BA'; // let fill = '#B4B6BA';
let fill = '#ABB3C2';
let trashFill = '#4B515A';
if (isOpen) { if (isOpen) {
fill = '#007EFF'; fill = '#007EFF';
trashFill = '#007EFF';
} }
if (hasErrors) { if (hasErrors) {
fill = '#F64D0A'; fill = '#FAA684';
trashFill = '#F64D0A';
} }
return ` return `
@ -155,6 +160,13 @@ const BannerWrapper = styled.button`
fill: ${fill} !important; fill: ${fill} !important;
} }
} }
.trash-icon {
svg {
path {
fill: ${trashFill} !important;
}
}
}
`; `;
}} }}

View File

@ -33,10 +33,13 @@ const EditViewDataManagerProvider = ({
initialData, initialData,
isLoading, isLoading,
modifiedData, modifiedData,
modifiedDZName,
shouldShowLoadingState, shouldShowLoadingState,
shouldCheckErrors, shouldCheckErrors,
} = reducerState.toJS(); } = reducerState.toJS();
console.log({ formErrors });
const currentContentTypeLayout = get(allLayoutData, ['contentType'], {}); const currentContentTypeLayout = get(allLayoutData, ['contentType'], {});
const abortController = new AbortController(); const abortController = new AbortController();
const { signal } = abortController; const { signal } = abortController;
@ -167,6 +170,17 @@ const EditViewDataManagerProvider = ({
await schema.validate(updatedData, { abortEarly: false }); await schema.validate(updatedData, { abortEarly: false });
} catch (err) { } catch (err) {
errors = getYupInnerErrors(err); errors = getYupInnerErrors(err);
if (modifiedDZName) {
errors = Object.keys(errors).reduce((acc, current) => {
const dzName = current.split('.')[0];
if (dzName !== modifiedDZName) {
acc[current] = errors[current];
}
return acc;
}, {});
}
} }
dispatch({ dispatch({
@ -313,10 +327,17 @@ const EditViewDataManagerProvider = ({
const removeComponentFromDynamicZone = (dynamicZoneName, index) => { const removeComponentFromDynamicZone = (dynamicZoneName, index) => {
emitEvent('removeComponentFromDynamicZone'); emitEvent('removeComponentFromDynamicZone');
const doesDZHaveError = Object.keys(formErrors).some(
key => key.split('.')[0] === dynamicZoneName
);
const shouldCheckErrors = !isEmpty(formErrors) && doesDZHaveError;
dispatch({ dispatch({
type: 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE', type: 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE',
dynamicZoneName, dynamicZoneName,
index, index,
shouldCheckErrors,
}); });
}; };
const removeComponentFromField = (keys, componentUid) => { const removeComponentFromField = (keys, componentUid) => {

View File

@ -9,6 +9,7 @@ const initialState = fromJS({
modifiedData: {}, modifiedData: {},
shouldShowLoadingState: false, shouldShowLoadingState: false,
shouldCheckErrors: false, shouldCheckErrors: false,
modifiedDZName: null,
}); });
const reducer = (state, action) => { const reducer = (state, action) => {
@ -57,6 +58,7 @@ const reducer = (state, action) => {
return fromJS([defaultDataStructure]); return fromJS([defaultDataStructure]);
}) })
.update('modifiedDZName', () => action.keys[0])
.update('shouldCheckErrors', v => { .update('shouldCheckErrors', v => {
if (action.shouldCheckErrors === true) { if (action.shouldCheckErrors === true) {
return !v; return !v;
@ -152,11 +154,15 @@ const reducer = (state, action) => {
}); });
} }
case 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE': case 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE':
return state.deleteIn([ return state
'modifiedData', .update('shouldCheckErrors', v => {
action.dynamicZoneName, if (action.shouldCheckErrors) {
action.index, return !v;
]); }
return v;
})
.deleteIn(['modifiedData', action.dynamicZoneName, action.index]);
case 'REMOVE_COMPONENT_FROM_FIELD': { case 'REMOVE_COMPONENT_FROM_FIELD': {
const componentPathToRemove = ['modifiedData', ...action.keys]; const componentPathToRemove = ['modifiedData', ...action.keys];
@ -190,7 +196,9 @@ const reducer = (state, action) => {
.update('isLoading', () => false) .update('isLoading', () => false)
.update('modifiedData', () => fromJS(action.contentTypeDataStructure)); .update('modifiedData', () => fromJS(action.contentTypeDataStructure));
case 'SET_ERRORS': case 'SET_ERRORS':
return state.update('formErrors', () => fromJS(action.errors)); return state
.update('modifiedDZName', () => null)
.update('formErrors', () => fromJS(action.errors));
case 'SUBMIT_ERRORS': case 'SUBMIT_ERRORS':
return state return state
.update('formErrors', () => fromJS(action.errors)) .update('formErrors', () => fromJS(action.errors))

View File

@ -1,4 +1,4 @@
import { get, isBoolean, isNaN } from 'lodash'; import { get, isBoolean, isEmpty, isNaN } from 'lodash';
import * as yup from 'yup'; import * as yup from 'yup';
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin'; import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
@ -10,6 +10,15 @@ yup.addMethod(yup.mixed, 'defined', function() {
); );
}); });
yup.addMethod(yup.array, 'notEmptyMin', function(min) {
return this.test('notEmptyMin', errorsTrads.min, value => {
if (isEmpty(value)) {
return true;
}
return value.length > min;
});
});
const getAttributes = data => get(data, ['schema', 'attributes'], {}); const getAttributes = data => get(data, ['schema', 'attributes'], {});
const createYupSchema = (model, { components }) => { const createYupSchema = (model, { components }) => {
@ -110,7 +119,7 @@ const createYupSchema = (model, { components }) => {
} }
} else { } else {
if (min) { if (min) {
dynamicZoneSchema = dynamicZoneSchema.min(min, errorsTrads.min); dynamicZoneSchema = dynamicZoneSchema.notEmptyMin(min);
} }
} }

View File

@ -108,6 +108,10 @@ const forms = {
schema = schema.integer(); schema = schema.integer();
} }
if (attributeType === 'dynamiczone') {
schema = schema.positive();
}
return schema.nullable(); return schema.nullable();
}), }),
min: yup.lazy(() => { min: yup.lazy(() => {
@ -121,14 +125,15 @@ const forms = {
schema = schema.integer(); schema = schema.integer();
} }
if (attributeType === 'dynamiczone') {
schema = schema.positive();
}
return schema return schema
.nullable() .nullable()
.when('max', (max, schema) => { .when('max', (max, schema) => {
if (max) { if (max) {
return schema.lessThan( return schema.max(max, getTrad('error.validation.minSupMax'));
max,
getTrad('error.validation.minSupMax')
);
} else { } else {
return schema; return schema;
} }
@ -146,7 +151,7 @@ const forms = {
.integer() .integer()
.when('maxLength', (maxLength, schema) => { .when('maxLength', (maxLength, schema) => {
if (maxLength) { if (maxLength) {
return schema.lessThan( return schema.max(
maxLength, maxLength,
getTrad('error.validation.minSupMax') getTrad('error.validation.minSupMax')
); );

View File

@ -41,6 +41,7 @@
"contentType.collectionName.description": "Useful when the name of your Content Type and your table name differ", "contentType.collectionName.description": "Useful when the name of your Content Type and your table name differ",
"contentType.collectionName.label": "Collection name", "contentType.collectionName.label": "Collection name",
"contentType.displayName.label": "Display name", "contentType.displayName.label": "Display name",
"error.validation.minSupMax": "Can't be superior",
"form.attribute.component.option.add": "Add a component", "form.attribute.component.option.add": "Add a component",
"form.attribute.component.option.create": "Create a new component", "form.attribute.component.option.create": "Create a new component",
"form.attribute.component.option.create.description": "A component is shared across types and components, it will be available and accessible everywhere.", "form.attribute.component.option.create.description": "A component is shared across types and components, it will be available and accessible everywhere.",
@ -96,6 +97,7 @@
"form.button.save": "Save", "form.button.save": "Save",
"form.button.select-component": "Select a component", "form.button.select-component": "Select a component",
"from": "from", "from": "from",
"injected-components.content-manager.edit-settings-view.link.content-types": "Edit the content type", "injected-components.content-manager.edit-settings-view.link.content-types": "Edit the content type",
"injected-components.content-manager.edit-settings-view.link.components": "Edit the component", "injected-components.content-manager.edit-settings-view.link.components": "Edit the component",
"menu.section.components.name.plural": "Components", "menu.section.components.name.plural": "Components",