Flash an error on save when required fields are missed
- Add error type constants - Flash an error message when the user saves without completing all required fields - Surface presence errors
This commit is contained in:
parent
b501db7239
commit
c97856c71d
@ -373,6 +373,15 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"doc"
|
"doc"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "aquibm",
|
||||||
|
"name": "Aquib Master",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/2936813?v=4",
|
||||||
|
"profile": "http://aquibm.com/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Netlify CMS
|
# Netlify CMS
|
||||||
[![All Contributors](https://img.shields.io/badge/all_contributors-47-orange.svg?style=flat-square)](#contributors)
|
[![All Contributors](https://img.shields.io/badge/all_contributors-48-orange.svg?style=flat-square)](#contributors)
|
||||||
|
|
||||||
A CMS for static site generators. Give non-technical users a simple way to edit
|
A CMS for static site generators. Give non-technical users a simple way to edit
|
||||||
and add content to any site built with a static site generator.
|
and add content to any site built with a static site generator.
|
||||||
@ -54,7 +54,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
|
|||||||
| [<img src="https://avatars3.githubusercontent.com/u/1923476?v=4" width="100px;"/><br /><sub>Kalin Chernev</sub>](https://github.com/kalinchernev)<br /> | [<img src="https://avatars2.githubusercontent.com/u/5018268?v=4" width="100px;"/><br /><sub>tortilaman</sub>](https://github.com/tortilaman)<br /> | [<img src="https://avatars0.githubusercontent.com/u/1184334?v=4" width="100px;"/><br /><sub>Václav Klecanda</sub>](http://www.vxk.cz)<br /> | [<img src="https://avatars0.githubusercontent.com/u/2983665?v=4" width="100px;"/><br /><sub>DrianHillman</sub>](http://drianhillman.me)<br /> | [<img src="https://avatars1.githubusercontent.com/u/1198032?v=4" width="100px;"/><br /><sub>Sean Crawford</sub>](https://github.com/seantcoyote)<br /> | [<img src="https://avatars1.githubusercontent.com/u/111932?v=4" width="100px;"/><br /><sub>Robert Riemann</sub>](http://blog.riemann.cc)<br /> | [<img src="https://avatars1.githubusercontent.com/u/4165105?v=4" width="100px;"/><br /><sub>Ben Berman</sub>](http://jygabyte.com)<br /> |
|
| [<img src="https://avatars3.githubusercontent.com/u/1923476?v=4" width="100px;"/><br /><sub>Kalin Chernev</sub>](https://github.com/kalinchernev)<br /> | [<img src="https://avatars2.githubusercontent.com/u/5018268?v=4" width="100px;"/><br /><sub>tortilaman</sub>](https://github.com/tortilaman)<br /> | [<img src="https://avatars0.githubusercontent.com/u/1184334?v=4" width="100px;"/><br /><sub>Václav Klecanda</sub>](http://www.vxk.cz)<br /> | [<img src="https://avatars0.githubusercontent.com/u/2983665?v=4" width="100px;"/><br /><sub>DrianHillman</sub>](http://drianhillman.me)<br /> | [<img src="https://avatars1.githubusercontent.com/u/1198032?v=4" width="100px;"/><br /><sub>Sean Crawford</sub>](https://github.com/seantcoyote)<br /> | [<img src="https://avatars1.githubusercontent.com/u/111932?v=4" width="100px;"/><br /><sub>Robert Riemann</sub>](http://blog.riemann.cc)<br /> | [<img src="https://avatars1.githubusercontent.com/u/4165105?v=4" width="100px;"/><br /><sub>Ben Berman</sub>](http://jygabyte.com)<br /> |
|
||||||
| [<img src="https://avatars3.githubusercontent.com/u/4349324?v=4" width="100px;"/><br /><sub>Benjamin Kniffler</sub>](https://github.com/bkniffler)<br /> | [<img src="https://avatars1.githubusercontent.com/u/845983?v=4" width="100px;"/><br /><sub>Mike Wickett</sub>](http://www.wickett.ca)<br /> | [<img src="https://avatars1.githubusercontent.com/u/2751799?v=4" width="100px;"/><br /><sub>Rory Claasen</sub>](http://roryclaasen.me)<br /> | [<img src="https://avatars3.githubusercontent.com/u/724844?v=4" width="100px;"/><br /><sub>Frederic Brodbeck</sub>](http://www.freder.io/)<br /> | [<img src="https://avatars2.githubusercontent.com/u/1245746?v=4" width="100px;"/><br /><sub>Stuart Dum</sub>](https://github.com/simplystuart)<br /> | [<img src="https://avatars0.githubusercontent.com/u/8184251?v=4" width="100px;"/><br /><sub>Ryan Watters</sub>](https://github.com/rdwatters)<br /> | [<img src="https://avatars3.githubusercontent.com/u/4315?v=4" width="100px;"/><br /><sub>Helder S Ribeiro</sub>](https://twitter.com/hsribei)<br /> |
|
| [<img src="https://avatars3.githubusercontent.com/u/4349324?v=4" width="100px;"/><br /><sub>Benjamin Kniffler</sub>](https://github.com/bkniffler)<br /> | [<img src="https://avatars1.githubusercontent.com/u/845983?v=4" width="100px;"/><br /><sub>Mike Wickett</sub>](http://www.wickett.ca)<br /> | [<img src="https://avatars1.githubusercontent.com/u/2751799?v=4" width="100px;"/><br /><sub>Rory Claasen</sub>](http://roryclaasen.me)<br /> | [<img src="https://avatars3.githubusercontent.com/u/724844?v=4" width="100px;"/><br /><sub>Frederic Brodbeck</sub>](http://www.freder.io/)<br /> | [<img src="https://avatars2.githubusercontent.com/u/1245746?v=4" width="100px;"/><br /><sub>Stuart Dum</sub>](https://github.com/simplystuart)<br /> | [<img src="https://avatars0.githubusercontent.com/u/8184251?v=4" width="100px;"/><br /><sub>Ryan Watters</sub>](https://github.com/rdwatters)<br /> | [<img src="https://avatars3.githubusercontent.com/u/4315?v=4" width="100px;"/><br /><sub>Helder S Ribeiro</sub>](https://twitter.com/hsribei)<br /> |
|
||||||
| [<img src="https://avatars1.githubusercontent.com/u/979966?v=4" width="100px;"/><br /><sub>Artem Govorov</sub>](http://dm.gl)<br /> | [<img src="https://avatars3.githubusercontent.com/u/2683300?v=4" width="100px;"/><br /><sub>Cédric Delpoux</sub>](http://xuopled.netlify.com/)<br /> | [<img src="https://avatars3.githubusercontent.com/u/83225?v=4" width="100px;"/><br /><sub>imorente</sub>](https://github.com/imorente)<br /> | [<img src="https://avatars3.githubusercontent.com/u/5230460?v=4" width="100px;"/><br /><sub>David Francoeur</sub>](http://davidfrancoeur.com)<br /> | [<img src="https://avatars3.githubusercontent.com/u/1954977?v=4" width="100px;"/><br /><sub>Rusta</sub>](https://github.com/Rusta)<br /> | [<img src="https://avatars1.githubusercontent.com/u/1299786?v=4" width="100px;"/><br /><sub>Henrik Lau Eriksson</sub>](http://henrik.laueriksson.com)<br /> | [<img src="https://avatars0.githubusercontent.com/u/1294877?v=4" width="100px;"/><br /><sub>Kraig Walker</sub>](https://www.kraigwalker.com)<br /> |
|
| [<img src="https://avatars1.githubusercontent.com/u/979966?v=4" width="100px;"/><br /><sub>Artem Govorov</sub>](http://dm.gl)<br /> | [<img src="https://avatars3.githubusercontent.com/u/2683300?v=4" width="100px;"/><br /><sub>Cédric Delpoux</sub>](http://xuopled.netlify.com/)<br /> | [<img src="https://avatars3.githubusercontent.com/u/83225?v=4" width="100px;"/><br /><sub>imorente</sub>](https://github.com/imorente)<br /> | [<img src="https://avatars3.githubusercontent.com/u/5230460?v=4" width="100px;"/><br /><sub>David Francoeur</sub>](http://davidfrancoeur.com)<br /> | [<img src="https://avatars3.githubusercontent.com/u/1954977?v=4" width="100px;"/><br /><sub>Rusta</sub>](https://github.com/Rusta)<br /> | [<img src="https://avatars1.githubusercontent.com/u/1299786?v=4" width="100px;"/><br /><sub>Henrik Lau Eriksson</sub>](http://henrik.laueriksson.com)<br /> | [<img src="https://avatars0.githubusercontent.com/u/1294877?v=4" width="100px;"/><br /><sub>Kraig Walker</sub>](https://www.kraigwalker.com)<br /> |
|
||||||
| [<img src="https://avatars1.githubusercontent.com/u/2994311?v=4" width="100px;"/><br /><sub>Rich Cook</sub>](http://www.TalesofMurder.com)<br /> | [<img src="https://avatars3.githubusercontent.com/u/1309950?v=4" width="100px;"/><br /><sub>Damien Van Der Windt</sub>](https://github.com/damienvdw)<br /> | [<img src="https://avatars2.githubusercontent.com/u/1571899?v=4" width="100px;"/><br /><sub>Matt Jared</sub>](http://mattjared.github.io/)<br /> | [<img src="https://avatars1.githubusercontent.com/u/1100280?v=4" width="100px;"/><br /><sub>bruce-one</sub>](https://github.com/bruce-one)<br /> | [<img src="https://avatars3.githubusercontent.com/u/103008?v=4" width="100px;"/><br /><sub>Frank Taillandier</sub>](https://frank.taillandier.me)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=DirtyF "Documentation") |
|
| [<img src="https://avatars1.githubusercontent.com/u/2994311?v=4" width="100px;"/><br /><sub>Rich Cook</sub>](http://www.TalesofMurder.com)<br /> | [<img src="https://avatars3.githubusercontent.com/u/1309950?v=4" width="100px;"/><br /><sub>Damien Van Der Windt</sub>](https://github.com/damienvdw)<br /> | [<img src="https://avatars2.githubusercontent.com/u/1571899?v=4" width="100px;"/><br /><sub>Matt Jared</sub>](http://mattjared.github.io/)<br /> | [<img src="https://avatars1.githubusercontent.com/u/1100280?v=4" width="100px;"/><br /><sub>bruce-one</sub>](https://github.com/bruce-one)<br /> | [<img src="https://avatars3.githubusercontent.com/u/103008?v=4" width="100px;"/><br /><sub>Frank Taillandier</sub>](https://frank.taillandier.me)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=DirtyF "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/2936813?v=4" width="100px;"/><br /><sub>Aquib Master</sub>](http://aquibm.com/)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=aquibm "Code") |
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
||||||
|
@ -7,6 +7,7 @@ import { getIntegrationProvider } from '../integrations';
|
|||||||
import { getAsset, selectIntegration } from '../reducers';
|
import { getAsset, selectIntegration } from '../reducers';
|
||||||
import { selectFields } from '../reducers/collections';
|
import { selectFields } from '../reducers/collections';
|
||||||
import { createEntry } from '../valueObjects/Entry';
|
import { createEntry } from '../valueObjects/Entry';
|
||||||
|
import ValidationErrorTypes from '../constants/validationErrorTypes';
|
||||||
|
|
||||||
const { notifSend } = notifActions;
|
const { notifSend } = notifActions;
|
||||||
|
|
||||||
@ -265,9 +266,23 @@ export function persistEntry(collection) {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const entryDraft = state.entryDraft;
|
const entryDraft = state.entryDraft;
|
||||||
|
const fieldsErrors = entryDraft.get('fieldsErrors');
|
||||||
|
|
||||||
// Early return if draft contains validation errors
|
// Early return if draft contains validation errors
|
||||||
if (!entryDraft.get('fieldsErrors').isEmpty()) return Promise.reject();
|
if (!fieldsErrors.isEmpty()) {
|
||||||
|
const hasPresenceErrors = fieldsErrors
|
||||||
|
.some(errors => errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE));
|
||||||
|
|
||||||
|
if (hasPresenceErrors) {
|
||||||
|
dispatch(notifSend({
|
||||||
|
message: 'Oops, you\'ve missed a required field. Please complete before saving.',
|
||||||
|
kind: 'danger',
|
||||||
|
dismissAfter: 8000,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
const backend = currentBackend(state.config);
|
const backend = currentBackend(state.config);
|
||||||
const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
|
const assetProxies = entryDraft.get('mediaFiles').map(path => getAsset(state, path));
|
||||||
|
@ -37,9 +37,11 @@ export default class ControlPane extends Component {
|
|||||||
<label className={labelClass} htmlFor={fieldName}>{field.get('label')}</label>
|
<label className={labelClass} htmlFor={fieldName}>{field.get('label')}</label>
|
||||||
<ul className="nc-controlPane-errors">
|
<ul className="nc-controlPane-errors">
|
||||||
{
|
{
|
||||||
errors && errors.map(error => (
|
errors && errors.map(error =>
|
||||||
typeof error === 'string' && <li key={error.trim().replace(/[^a-z0-9]+/gi, '-')}>{error}</li>
|
error.message &&
|
||||||
))
|
typeof error.message === 'string' &&
|
||||||
|
<li key={error.message.trim().replace(/[^a-z0-9]+/gi, '-')}>{error.message}</li>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<ControlHOC
|
<ControlHOC
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||||
|
import ValidationErrorTypes from '../../constants/validationErrorTypes';
|
||||||
|
|
||||||
const truthy = () => ({ error: false });
|
const truthy = () => ({ error: false });
|
||||||
|
|
||||||
@ -57,7 +58,12 @@ class ControlHOC extends Component {
|
|||||||
(value.hasOwnProperty('length') && value.length === 0) ||
|
(value.hasOwnProperty('length') && value.length === 0) ||
|
||||||
(value.constructor === Object && Object.keys(value).length === 0)
|
(value.constructor === Object && Object.keys(value).length === 0)
|
||||||
)) {
|
)) {
|
||||||
return { error: true };
|
const error = {
|
||||||
|
type: ValidationErrorTypes.PRESENCE,
|
||||||
|
message: `${ field.get('label', field.get('name')) } is required.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { error };
|
||||||
}
|
}
|
||||||
return { error: false };
|
return { error: false };
|
||||||
}
|
}
|
||||||
@ -65,8 +71,14 @@ class ControlHOC extends Component {
|
|||||||
validatePattern(field, value) {
|
validatePattern(field, value) {
|
||||||
const pattern = field.get('pattern', false);
|
const pattern = field.get('pattern', false);
|
||||||
if (pattern && !RegExp(pattern.first()).test(value)) {
|
if (pattern && !RegExp(pattern.first()).test(value)) {
|
||||||
return { error: `${ field.get('label', field.get('name')) } didn't match the pattern: ${ pattern.last() }` };
|
const error = {
|
||||||
|
type: ValidationErrorTypes.PATTERN,
|
||||||
|
message: `${ field.get('label', field.get('name')) } didn't match the pattern: ${ pattern.last() }`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { error: false };
|
return { error: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,11 +92,22 @@ class ControlHOC extends Component {
|
|||||||
} else if (response instanceof Promise) {
|
} else if (response instanceof Promise) {
|
||||||
response.then(
|
response.then(
|
||||||
() => { this.validate({ error: false }); },
|
() => { this.validate({ error: false }); },
|
||||||
(error) => {
|
(err) => {
|
||||||
this.validate({ error: `${ field.get('label', field.get('name')) } - ${ error }.` });
|
const error = {
|
||||||
|
type: ValidationErrorTypes.CUSTOM,
|
||||||
|
message: `${ field.get('label', field.get('name')) } - ${ err }.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.validate({ error });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return { error: `${ field.get('label', field.get('name')) } is processing.` };
|
|
||||||
|
const error = {
|
||||||
|
type: ValidationErrorTypes.CUSTOM,
|
||||||
|
message: `${ field.get('label', field.get('name')) } is processing.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { error };
|
||||||
}
|
}
|
||||||
return { error: false };
|
return { error: false };
|
||||||
};
|
};
|
||||||
|
5
src/constants/validationErrorTypes.js
Normal file
5
src/constants/validationErrorTypes.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
PRESENCE: 'PRESENCE',
|
||||||
|
PATTERN: 'PATTERN',
|
||||||
|
CUSTOM: 'CUSTOM',
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user