Delete useless components in ctm and remove redux from app container in upload

This commit is contained in:
cyril lopez 2018-02-23 11:06:30 +01:00
parent 91f9482480
commit c95d413716
35 changed files with 10 additions and 3448 deletions

View File

@ -1,660 +0,0 @@
/**
*
* Input
*
*/
import React from 'react';
import moment from 'moment';
import PropTypes from 'prop-types';
import { get, isEmpty, map, mapKeys, isObject, reject, includes, upperFirst } from 'lodash';
import { FormattedMessage } from 'react-intl';
// import DateTime from 'react-datetime';
// import DateTimeStyle from 'react-datetime/css/react-datetime.css';
import styles from './styles.scss';
class Input extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
errors: [],
hasInitialValue: false,
showPassword: false,
isFocus: false,
};
}
componentDidMount() {
if (this.props.value && !isEmpty(this.props.value)) {
this.setState({ hasInitialValue: true });
}
if (!isEmpty(this.props.errors)) {
this.setState({ errors: this.props.errors });
}
}
componentWillReceiveProps(nextProps) {
// Check if errors have been updated during validations
if (this.props.didCheckErrors !== nextProps.didCheckErrors) {
// Remove from the state errors that are already set
const errors = isEmpty(nextProps.errors) ? [] : nextProps.errors;
this.setState({ errors });
}
}
handleBlur = ({ target }) => {
// prevent error display if input is initially empty
if (!isEmpty(target.value) || this.state.hasInitialValue) {
// validates basic string validations
// add custom logic here such as alerts...
const errors = this.validate(target.value);
this.setState({ errors, hasInitialValue: true });
}
}
handleChangeCheckbox = (e) => {
const target = {
type: 'checkbox',
value: !this.props.value,
name: this.props.name,
};
this.props.onChange({ target });
}
handleBlurEmail = (e) => {
this.setState({ isFocus: !this.state.isFocus });
if (this.props.handleBlur) {
this.props.handleBlur(e);
} else {
this.handleBlur(e);
}
}
handleFocusEmail = (e) => {
this.setState({ isFocus: !this.state.isFocus });
if (this.props.onFocus) {
this.props.onFocus(e);
}
}
handleToggle = (e) => {
const target = {
type: 'toggle',
name: this.props.name,
value: e.target.id === 'on',
}
this.props.onChange({ target });
}
handleShowPassword = () => this.setState({ showPassword: !this.state.showPassword });
renderErrors = (errorStyles) => { // eslint-disable-line consistent-return
if (!this.props.noErrorsDescription) {
const divStyle = errorStyles || styles.errorContainer;
return (
map(this.state.errors, (error, key) => {
const displayError = isObject(error) && error.id
? <FormattedMessage {...error} values={{ errorMessage: error.errorMessage }} />
: error;
return (
<div key={key} className={`form-control-feedback invalid-feedback ${divStyle}`} style={{ display: 'block' }}>{displayError}</div>
);
})
);
}
}
renderInputCheckbox = (requiredClass, inputDescription) => {
const title = !isEmpty(this.props.title) ? <div className={styles.inputTitle}><FormattedMessage id={this.props.title} /></div> : '';
const spacer = !inputDescription ? <div /> : <div style={{ marginBottom: '.5rem'}}></div>;
return (
<div className={`${styles.inputCheckbox} ${requiredClass} ${this.props.customBootstrapClass || 'col-md-3'}`}>
<div className="form-check">
{title}
<FormattedMessage id={this.props.label} values={this.props.labelValues}>
{(message) => (
<label className={`${styles.checkboxLabel} form-check-label`} htmlFor={this.props.name}>
<input
className="form-check-input"
defaultChecked={this.props.value}
id={this.props.name}
name={this.props.name}
onChange={this.handleChangeCheckbox}
type="checkbox"
/>
{message}
</label>
)}
</FormattedMessage>
<div className={styles.inputCheckboxDescriptionContainer}>
<small>{inputDescription}</small>
</div>
</div>
{spacer}
</div>
)
}
// renderInputDate = (requiredClass, inputDescription) => {
// let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
//
// if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
// spacer = <div />;
// }
//
// const value = isObject(this.props.value) && this.props.value._isAMomentObject === true ?
// this.props.value :
// moment(this.props.value);
//
// return (
// <div className={`${styles.inputDate} ${styles.input} ${this.props.customBootstrapClass || 'col-md-4'} ${requiredClass}`}>
// <label htmlFor={this.props.label}>
// <FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
// </label>
// <DateTime
// value={value}
// dateFormat='YYYY-MM-DD'
// timeFormat='HH:mm:ss'
// tabIndex={this.props.tabIndex}
// utc={true}
// inputProps={{
// placeholder: this.props.placeholder,
// className: 'form-control',
// name: this.props.name,
// id: this.props.label,
// }}
// onChange={(moment) => this.props.onChange({ target: {
// name: this.props.name,
// value: moment
// }})}
// />
// <div className={styles.inputDescriptionContainer}>
// <small>{inputDescription}</small>
// </div>
// {this.renderErrors(styles.errorContainerTextArea)}
// {spacer}
// </div>
// )
// }
renderInputEmail = (requiredClass, inputDescription, handleBlur) => {
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
spacer = <div />;
}
return (
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} />
</label>
<div className={`input-group ${styles.input} ${styles.inputEmail}`} style={{ marginBottom: '1rem'}}>
<span className={`input-group-addon ${styles.addonEmail} ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) && !this.state.isFocus ? styles.errorAddon: ''}`} />
<FormattedMessage id={this.props.placeholder || this.props.label} values={this.props.labelValues}>
{(placeholder) => (
<input
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? `form-control-danger is-invalid ${styles.error}`: ''}`}
onChange={this.props.onChange}
value={this.props.value}
name={this.props.name}
id={this.props.label}
onBlur={this.handleBlurEmail}
onFocus={this.handleFocusEmail}
placeholder={placeholder}
disabled={this.props.disabled}
type="email"
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
</div>
<div className={styles.inputDescriptionContainer}>
<small>{inputDescription}</small>
</div>
{this.renderErrors()}
{spacer}
</div>
)
}
renderFormattedInput = (handleBlur, inputValue, placeholder) => (
<FormattedMessage id={`${placeholder}`} defaultMessage={placeholder}>
{(message) => (
<input
name={this.props.name}
id={this.props.label}
onBlur={handleBlur}
onFocus={this.props.onFocus}
onChange={this.props.onChange}
value={inputValue}
type={this.props.type}
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors)? 'form-control-danger is-invalid' : ''}`}
placeholder={message}
autoComplete="off"
disabled={this.props.disabled}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
)
renderInputPassword = (requiredClass, inputDescription, handleBlur) => {
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
spacer = <div />;
}
const color = this.state.showPassword ? { color: 'black' } : { color: '#9EA7B8' };
const type = this.state.showPassword ? 'text' : 'password';
return (
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
</label>
<FormattedMessage id={this.props.placeholder || this.props.label} values={this.props.labelValues}>
{(placeholder) => (
<input
autoComplete="new-password"
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
onChange={this.props.onChange}
value={this.props.value}
name={this.props.name}
id={this.props.label}
onBlur={handleBlur}
onFocus={this.props.onFocus}
placeholder={placeholder}
disabled={this.props.disabled}
type={type}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
<div className={styles.inputDescriptionContainer}>
<small>{inputDescription}</small>
</div>
{this.renderErrors()}
{spacer}
<div className={styles.insideInput} onClick={this.handleShowPassword} style={color}>
<i className="fa fa-eye" />
</div>
</div>
);
}
renderInputSelect = (requiredClass, inputDescription, handleBlur) => {
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
spacer = <div />;
}
return (
<div className={`${styles.input} ${requiredClass} ${this.props.customBootstrapClass || 'col-md-6'}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} />
</label>
<select
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
id={this.props.label}
name={this.props.name}
onChange={this.props.onChange}
value={this.props.value}
disabled={this.props.disabled}
onBlur={handleBlur}
tabIndex={this.props.tabIndex}
autoFocus={this.props.autoFocus}
>
{map(this.props.selectOptions, (option, key) => (
option.name ?
<FormattedMessage id={option.name} defaultMessage={option.name} values={{ option: option.name }} key={key}>
{(message) => (
<option value={option.value}>
{message}
</option>
)}
</FormattedMessage> :
<option value={option.value} key={key}>{option.value}</option>
))}
</select>
<div className={styles.inputDescriptionContainer}>
<small>{inputDescription}</small>
</div>
{this.renderErrors()}
{spacer}
</div>
);
}
renderInputSearch = (requiredClass, inputDescription, handleBlur) => {
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
spacer = <div />;
}
return (
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
</label>
<div className={`input-group ${styles.inputSearch}`} style={{ marginBottom: '1rem'}}>
<span className={`input-group-addon ${styles.addonSearch}`} />
<FormattedMessage id={this.props.placeholder || this.props.label} defaultMessage={this.props.label}>
{(placeholder) => (
<input
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
onChange={this.props.onChange}
value={this.props.value}
name={this.props.name}
id={this.props.label}
onBlur={handleBlur}
onFocus={this.props.onFocus}
placeholder={placeholder}
disabled={this.props.disabled}
type="text"
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
</div>
<div className={styles.inputDescriptionContainer}>
<small>{inputDescription}</small>
</div>
<div>
{this.renderErrors()}
{spacer}
</div>
</div>
);
}
renderInputTextArea = (requiredClass, inputDescription, handleBlur) => {
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
spacer = <div />;
}
return (
<div className={`${styles.inputTextArea} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
</label>
<FormattedMessage id={this.props.placeholder || this.props.label}>
{(placeholder) => (
<textarea
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
onChange={this.props.onChange}
value={this.props.value}
name={this.props.name}
id={this.props.label}
onBlur={handleBlur}
onFocus={this.props.onFocus}
placeholder={placeholder}
disabled={this.props.disabled}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
<div className={styles.inputTextAreaDescriptionContainer}>
<small>{inputDescription}</small>
</div>
{this.renderErrors(styles.errorContainerTextArea)}
{spacer}
</div>
)
}
renderInputToggle = () => {
const btnClassOff = this.props.value ? 'btn' : `btn ${styles.gradientOff}`;
const btnClassOn = this.props.value ? `btn ${styles.gradientOn}` : 'btn';
const spacer = this.props.inputDescription ? <div className={styles.spacer} /> : <div />;
const inputDescription = this.props.inputDescription ?
<FormattedMessage id={this.props.inputDescription} /> : '';
return (
<div className={`${this.props.customBootstrapClass || 'col-md-6'} ${styles.inputToggle}`}>
<div className={styles.toggleLabel}>
<FormattedMessage id={this.props.label} values={this.props.labelValues} />
</div>
<div className={`btn-group ${styles.inputToggleButtons}`}>
<button type="button" className={btnClassOff} id="off" onClick={this.handleToggle}>OFF</button>
<button type="button" className={btnClassOn} id="on" onClick={this.handleToggle}>ON</button>
</div>
<div className={styles.inputDescriptionContainer}>
<small>{inputDescription}</small>
</div>
{spacer}
</div>
);
}
render() {
const inputValue = this.props.value || '';
// override default onBlur
const handleBlur = this.props.onBlur || this.handleBlur;
const placeholder = this.props.placeholder || this.props.label;
const label = this.props.label ?
<label htmlFor={this.props.label}><FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} /></label>
: <label htmlFor={this.props.label} />;
const requiredClass = get(this.props.validations, 'required') && this.props.addRequiredInputDesign ?
styles.requiredClass : '';
const input = placeholder ? this.renderFormattedInput(handleBlur, inputValue, placeholder)
: <input
name={this.props.name}
id={this.props.label}
onBlur={handleBlur}
onFocus={this.props.onFocus}
onChange={this.props.onChange}
value={inputValue}
type={this.props.type}
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
placeholder={placeholder}
disabled={this.props.disabled}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>;
const link = !isEmpty(this.props.linkContent) ? <a href={this.props.linkContent.link} target="_blank"><FormattedMessage id={this.props.linkContent.description} /></a> : '';
let inputDescription = !isEmpty(this.props.inputDescription) ? <FormattedMessage id={this.props.inputDescription} /> : '';
if (!isEmpty(this.props.linkContent) && !isEmpty(this.props.inputDescription)) {
inputDescription = <FormattedMessage id='input.description' defaultMessage={`{description}, {link}`} values={{link, description: <FormattedMessage id={this.props.inputDescription} /> }} />;
}
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
spacer = <div />;
}
if (this.props.search) {
return this.renderInputSearch(requiredClass, inputDescription, handleBlur);
}
switch (this.props.type) {
case 'select':
return this.renderInputSelect(requiredClass, inputDescription, handleBlur);
case 'textarea':
return this.renderInputTextArea(requiredClass, inputDescription, handleBlur);
case 'checkbox':
return this.renderInputCheckbox(requiredClass, inputDescription);
// case 'date':
// return this.renderInputDate(requiredClass, inputDescription);
case 'password':
return this.renderInputPassword(requiredClass, inputDescription, handleBlur);
case 'toggle':
return this.renderInputToggle();
case 'email':
return this.renderInputEmail(requiredClass, inputDescription, handleBlur);
case 'search':
return this.renderInputSearch(requiredClass, inputDescription, handleBlur)
default:
}
const addonInput = this.props.addon ?
<div className={`input-group ${styles.input}`} style={{ marginBottom: '1rem'}}>
<span className={`input-group-addon ${styles.addon}`}><FormattedMessage id={this.props.addon} /></span>
{input}
</div> : input;
return (
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
{label}
{addonInput}
<div className={styles.inputDescriptionContainer}>
<small>{inputDescription}</small>
</div>
{this.renderErrors()}
{spacer}
</div>
);
}
validate = (value) => {
let errors = [];
const emailRegex = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
// handle i18n
const requiredError = { id: 'components.Input.error.validation.required' };
mapKeys(this.props.validations, (validationValue, validationKey) => {
switch (validationKey) {
case 'max':
if (parseInt(value, 10) > validationValue) {
errors.push({ id: 'components.Input.error.validation.max' });
}
break;
case 'maxLength':
if (value.length > validationValue) {
errors.push({ id: 'components.Input.error.validation.maxLength' });
}
break;
case 'min':
if (parseInt(value, 10) < validationValue) {
errors.push({ id: 'components.Input.error.validation.min' });
}
break;
case 'minLength':
if (value.length < validationValue) {
errors.push({ id: 'components.Input.error.validation.minLength' });
}
break;
case 'required':
if (value.length === 0) {
errors.push({ id: 'components.Input.error.validation.required' });
}
break;
case 'regex':
if (!new RegExp(validationValue).test(value)) {
errors.push({ id: 'components.Input.error.validation.regex' });
}
break;
default:
errors = [];
}
});
if (this.props.type === 'email' && !emailRegex.test(value)) {
errors.push({ id: 'components.Input.error.validation.email' });
}
if (includes(errors, requiredError)) {
errors = reject(errors, (error) => error !== requiredError);
}
return errors;
}
}
Input.propTypes = {
addon: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
]),
addRequiredInputDesign: PropTypes.bool,
autoFocus: PropTypes.bool,
customBootstrapClass: PropTypes.string,
deactivateErrorHighlight: PropTypes.bool,
didCheckErrors: PropTypes.bool,
disabled: PropTypes.bool,
errors: PropTypes.array,
inputDescription: PropTypes.string,
label: PropTypes.string.isRequired,
labelValues: PropTypes.object,
linkContent: PropTypes.shape({
link: PropTypes.string,
description: PropTypes.string,
}),
name: PropTypes.string.isRequired,
noErrorsDescription: PropTypes.bool,
onBlur: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.func,
]),
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.func,
]),
placeholder: PropTypes.string,
search: PropTypes.bool,
selectOptions: PropTypes.array,
selectOptionsFetchSucceeded: PropTypes.bool,
tabIndex: PropTypes.string,
title: PropTypes.string,
type: PropTypes.string.isRequired,
validations: PropTypes.object.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.number,
]),
};
Input.defaultProps = {
addon: false,
addRequiredInputDesign: false,
autoFocus: false,
deactivateErrorHighlight: false,
didCheckErrors: false,
disabled: false,
errors: [],
inputDescription: '',
labelValues: {},
linkContent: {},
noErrorsDescription: false,
onBlur: false,
onFocus: () => {},
placeholder: '',
search: false,
selectOptions: [],
selectOptionsFetchSucceeded: false,
tabIndex: '0',
value: ''
};
export default Input;

View File

@ -1,371 +0,0 @@
/* stylelint-disable */
.error {
border-left: none!important;
}
.errorAddon {
border: 1px solid #ff203c!important;
border-right: none!important;
}
.errorContainer {
margin-top: .5rem;
margin-bottom: .5rem;
line-height: 1.3rem;
font-size: 1.3rem;
}
.errorContainerTextArea {
margin-top: .5rem;
margin-bottom: .5rem;
line-height: 1.3rem;
font-size: 1.3rem;
}
.addon {
width: 5.9rem;
height: 3.4rem;
margin-top: .9rem;
background-color: rgba(16, 22, 34, 0.02);
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
color: rgba(16, 22, 34, 0.5);
line-height: 3.2rem;
font-size: 1.3rem;
font-family: 'Lato';
font-weight: 600!important;
text-transform: capitalize;
-moz-appearance: none;
-webkit-appearance: none;
}
.addonSearch {
width: 3.2rem;
height: 3.4rem;
margin-top: .9rem;
background-color: rgba(16, 22, 34, 0.02);
border: 1px solid #E3E9F3!important;
border-right: none !important;
border-radius: 0.25rem;
border-bottom-left-radius: 0;
color: rgba(16, 22, 34, 0.5);
line-height: 3.2rem;
font-size: 1.3rem;
font-family: 'Lato';
font-weight: 600!important;
text-transform: capitalize;
-moz-appearance: none;
-webkit-appearance: none;
&:before {
content: '\F002';
display: inline-table;
margin-top: 0px;
margin-left: 2px;
color: #B3B5B9;
font-weight: 400;
font-size: 14px;
font-family: 'FontAwesome';
-webkit-font-smoothing: antialiased;
}
}
.addonEmail {
width: 3.4rem;
height: 3.4rem;
margin-top: .9rem;
padding-left: 0.9rem;
background-color: rgba(16, 22, 34, 0.02);
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
color: rgba(16, 22, 34, 0.5);
line-height: 3.2rem;
font-size: 1.3rem;
font-family: 'Lato';
font-weight: 600!important;
text-transform: capitalize;
-moz-appearance: none;
-webkit-appearance: none;
-webkit-font-smoothing: antialiased;
&:after {
content: '@';
display: inline-table;
color: #B3B5B9;
font-size: 16px;
font-weight: 900;
font-family: Lato;
}
& + input {
border-left: 0px !important;
}
}
.insideInput {
position: absolute;
top: 3.5rem;
right: 2.7rem;
color: #9EA7B8;
&:hover {
color: black!important;
}
}
.inputEmail {
> input {
&:focus {
border-color: #E3E9F3;
}
}
& + span {
border-color: #E3E9F3;
}
}
.input {
min-width: 200px;
margin-bottom: 1.5rem;
font-size: 1.3rem;
label {
margin-bottom: 0;
font-weight: 500;
}
input {
height: 3.4rem;
margin-top: .9rem;
padding-left: 1rem;
background-size: 0 !important;
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
line-height: 3.4rem;
font-size: 1.3rem;
font-family: 'Lato' !important;
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
}
select {
height: 3.4rem !important;
margin-top: .9rem;
padding-top: 0rem;
padding-left: 1rem;
background-position: right -1px center;
background-repeat: no-repeat;
background-image: url('../../assets/images/background_input.svg');
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
line-height: 3.2rem;
font-size: 1.3rem;
font-family: 'Lato' !important;
-moz-appearance: none;
-webkit-appearance: none;
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
}
}
.inputCheckbox {
margin-bottom: 1.5rem;
font-size: 1.3rem;
font-family: 'Lato';
> div {
margin-bottom: 0;
}
}
.checkboxLabel {
font-weight: 400;
> input {
margin-right: 1.2rem;
}
}
.inputCheckboxDescriptionContainer {
width: 150%;
padding-top: .2rem;
padding-left: 2.5rem;
line-height: 1.2rem;
cursor: pointer;
> small {
color: #9EA7B8;
font-family: 'Lato';
font-size: 1.2rem;
}
}
.inputDescriptionContainer {
width: 200%;
margin-top: 1.3rem;
line-height: 1.2rem;
> small {
color: #9EA7B8;
font-family: 'Lato';
font-size: 1.2rem;
}
}
.inputTextAreaDescriptionContainer {
margin-top: 1.3rem;
line-height: 1.2rem;
> small {
color: #9EA7B8;
font-family: 'Lato';
font-size: 1.2rem;
}
}
.inputTextArea {
margin-bottom: 1.5rem;
font-size: 1.3rem;
font-family: 'Lato';
> label {
margin-bottom: 0;
font-weight: 500;
}
> textarea {
height: 10.6rem;
margin-top: .9rem;
padding: .7rem 1rem .3rem 1rem;
background-size: 0 !important;
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
line-height: 1.8rem;
font-size: 1.3rem;
font-family: 'Lato' !important;
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
}
}
.inputDate{
> div:first-of-type{
&:before{
content: '\f073';
position: absolute;
left: 1px; top: 1px;
width: 32px;
height: 32px;
border-radius: 3px 0px 0px 3px;
background: #FAFAFB;
color: #B3B5B9;
text-align: center;
font-family: 'FontAwesome';
font-size: 1.4rem;
line-height: 32px;
-webkit-font-smoothing: none;
}
input {
width: 100%;
padding-left: 42px;
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
&:focus{
outline: none;
}
}
}
}
.inputSearch {
min-width: 200px;
margin-bottom: 1.5rem;
font-size: 1.3rem;
label {
margin-bottom: 0;
font-weight: 500;
}
input {
height: 3.4rem;
margin-top: .9rem;
padding-left: 1rem;
background-size: 0 !important;
border: 1px solid #E3E9F3;
// border-left: none;
border-radius: 0.25rem;
border-bottom-right-radius: 0;
line-height: 3.4rem;
font-size: 1.3rem;
font-family: 'Lato' !important;
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
&:focus {
// border-left: 1px solid #78caff!important;
border-color: #78caff;
}
}
}
.inputTitle {
margin-bottom: 1.7rem;
font-weight: 500;
text-transform: capitalize;
}
.spacer {
height: .5rem;
}
.inputToggle {
margin-bottom: 1.5rem;
font-size: 1.3rem;
}
.inputToggleButtons {
padding-top: 9px;
> button {
width: 5.3rem;
height: 3.4rem;
margin-bottom: 2.8rem;
padding: 0;
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
color: #CED3DB;
background-color: white;
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
font-weight: 600;
font-size: 1.2rem;
letter-spacing: 0.1rem;
font-family: Lato;
line-height: 3.4rem;
cursor: pointer;
&:first-of-type {
border-right: none;
}
&:nth-of-type(2) {
border-left: none;
}
&:hover {
z-index: 0 !important;
}
&:focus {
outline: 0;
box-shadow: 0 0 0;
}
}
}
.toggleLabel {
margin-bottom: 0;
font-weight: 500;
}
.gradientOff {
background-image: linear-gradient( to bottom right, #F65A1D, #F68E0E );
color: white !important;
box-shadow: inset -1px 1px 3px rgba(0,0,0,0.1);
}
.gradientOn {
background-image: linear-gradient( to bottom right, #005EEA, #0097F6);
color: white !important;
box-shadow: inset 1px 1px 3px rgba(0,0,0,0.1);
}

View File

@ -1,122 +0,0 @@
/**
*
* EditForm
*
*/
// Dependencies.
import React from 'react';
import PropTypes from 'prop-types';
import { findIndex, get, omit, isFunction, merge } from 'lodash';
// Components.
import Input from 'components/Input';
// Utils.
import getQueryParameters from 'utils/getQueryParameters';
// Styles.
import styles from './styles.scss';
class EditForm extends React.Component {
constructor(props) {
super(props);
}
getInputType = (type = '') => {
switch (type.toLowerCase()) {
case 'password':
return 'password';
case 'boolean':
return 'checkbox';
case 'text':
return 'textarea';
case 'email':
return 'email';
case 'string':
return 'text';
case 'date':
case 'datetime':
return 'date';
case 'float':
case 'integer':
case 'bigint':
case 'decimal':
return 'number';
default:
return 'text';
}
}
render() {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
const currentLayout = get(this.props.layout, [this.props.currentModelName, 'attributes']);
// Remove `id` field
const displayedFields = merge(get(currentLayout), omit(currentSchema.fields, 'id'));
// List fields inputs
const fields = Object.keys(displayedFields).map((attr, key) => {
const details = displayedFields[attr];
const errorIndex = findIndex(this.props.formErrors, ['name', attr]);
const errors = errorIndex !== -1 ? this.props.formErrors[errorIndex].errors : [];
const validationsIndex = findIndex(this.props.formValidations, ['name', attr]);
const validations = get(this.props.formValidations[validationsIndex], 'validations') || {};
const layout = Object.keys(get(currentLayout, attr, {})).reduce((acc, current) => {
acc[current] = isFunction(currentLayout[attr][current]) ?
currentLayout[attr][current](this) :
currentLayout[attr][current];
return acc;
}, {});
return (
<Input
autoFocus={key === 0}
key={attr}
type={get(layout, 'type', this.getInputType(details.type))}
label={get(layout, 'label') || details.label || ''}
name={attr}
customBootstrapClass={get(layout, 'className') || ''}
value={this.props.record.get(attr) || ''}
placeholder={get(layout, 'placeholder') || details.placeholder || details.label || attr || ''}
onChange={this.props.onChange}
validations={get(layout, 'validations') || validations}
errors={errors}
didCheckErrors={this.props.didCheckErrors}
pluginID="content-manager"
/>
);
});
return (
<form className={styles.form} onSubmit={this.props.onSubmit}>
<div className='row'>
{fields}
</div>
</form>
);
}
}
EditForm.propTypes = {
currentModelName: PropTypes.string.isRequired,
didCheckErrors: PropTypes.bool.isRequired,
formErrors: PropTypes.array.isRequired,
formValidations: PropTypes.array.isRequired,
layout: PropTypes.object.isRequired,
location: PropTypes.shape({
search: PropTypes.string,
}).isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
record: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,
]).isRequired,
schema: PropTypes.object.isRequired,
};
export default EditForm;

View File

@ -1,4 +0,0 @@
.form { /* stylelint-disable */
width: 100%;
padding: 0 15px;
}

View File

@ -1,109 +0,0 @@
/**
*
* EditFormRelations
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { get, map, size } from 'lodash';
// Components.
import SelectOne from 'components/SelectOne';
import SelectMany from 'components/SelectMany';
// Utils.
import getQueryParameters from 'utils/getQueryParameters';
// Style.
import styles from './styles.scss';
class EditFormRelations extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentDidMount() {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
if (size(get(currentSchema, ['relations'])) === 0 && !this.props.isNull) {
this.props.toggleNull();
}
}
render() {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
const relations = map(currentSchema.relations, (relation, i) => {
switch (relation.nature) {
case 'oneWay':
case 'oneToOne':
case 'manyToOne':
if (relation.dominant) {
return (
<SelectOne
currentModelName={this.props.currentModelName}
key={i}
record={this.props.record}
relation={relation}
schema={this.props.schema}
setRecordAttribute={this.props.setRecordAttribute}
location={this.props.location}
/>
);
}
break;
case 'oneToMany':
case 'manyToMany':
return (
<SelectMany
currentModelName={this.props.currentModelName}
key={i}
record={this.props.record}
relation={relation}
schema={this.props.schema}
setRecordAttribute={this.props.setRecordAttribute}
location={this.props.location}
/>
);
default:
break;
}
});
if (!relations.length) {
return (null);
}
return (
<div className={styles.editFormRelations}>
<h3>Relational data</h3>
{relations}
</div>
);
}
}
EditFormRelations.propTypes = {
currentModelName: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
]).isRequired,
isNull: PropTypes.bool.isRequired,
location: PropTypes.shape({
search: PropTypes.string,
}).isRequired,
record: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,
]).isRequired,
schema: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,
]).isRequired,
setRecordAttribute: PropTypes.func.isRequired,
toggleNull: PropTypes.func.isRequired,
};
export default EditFormRelations;

View File

@ -1,13 +0,0 @@
.editFormRelations { /* stylelint-disable */
h3{
height: 41px;
margin: 0 -25px 14px;
padding: 0 25px;
background: #F3F3F3;
line-height: 41px;
border-radius: 2px 2px 0 0;
letter-spacing: 0.03rem;
font-size: 1.3rem;
font-weight: bold;
}
}

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="23px" height="32px" viewBox="0 0 23 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Inputs/Select-(col-6)" transform="translate(-335.000000, -1.000000)">
<g id="input">
<g id="Number" transform="translate(335.017699, 1.000000)">
<g id="Group">
<path d="M-5.68434189e-14,0 L20,0 L20,0 C21.1045695,-2.02906125e-16 22,0.8954305 22,2 L22,30 L22,30 C22,31.1045695 21.1045695,32 20,32 L-5.68434189e-14,32 L-5.68434189e-14,0 Z" id="Rectangle" fill="#FAFAFB"></path>
<g id="Carets" transform="translate(8.000000, 11.000000)" fill-rule="nonzero" fill="#B3B5B9">
<g id="caret-down" transform="translate(0.000000, 7.000000)">
<path d="M6,0.375 C6,0.4765625 5.96289062,0.564453125 5.88867188,0.638671875 L3.26367188,3.26367188 C3.18945312,3.33789063 3.1015625,3.375 3,3.375 C2.8984375,3.375 2.81054688,3.33789063 2.73632812,3.26367188 L0.111328125,0.638671875 C0.037109375,0.564453125 0,0.4765625 0,0.375 C0,0.2734375 0.037109375,0.185546875 0.111328125,0.111328125 C0.185546875,0.037109375 0.2734375,0 0.375,0 L5.625,0 C5.7265625,0 5.81445312,0.037109375 5.88867188,0.111328125 C5.96289062,0.185546875 6,0.2734375 6,0.375 Z" id="Shape"></path>
</g>
<g id="caret-top" transform="translate(3.000000, 2.375000) rotate(180.000000) translate(-3.000000, -2.375000) translate(0.000000, 0.375000)">
<path d="M6,0.375 C6,0.4765625 5.96289062,0.564453125 5.88867187,0.638671875 L3.26367187,3.26367188 C3.18945312,3.33789063 3.1015625,3.375 3,3.375 C2.8984375,3.375 2.81054687,3.33789063 2.73632812,3.26367188 L0.111328125,0.638671875 C0.037109375,0.564453125 -1.77635684e-15,0.4765625 -1.77635684e-15,0.375 C-1.77635684e-15,0.2734375 0.037109375,0.185546875 0.111328125,0.111328125 C0.185546875,0.037109375 0.2734375,1.77635684e-15 0.375,1.77635684e-15 L5.625,1.77635684e-15 C5.7265625,1.77635684e-15 5.81445312,0.037109375 5.88867187,0.111328125 C5.96289062,0.185546875 6,0.2734375 6,0.375 Z" id="Shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,55 +0,0 @@
/**
*
* LimitSelect
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { map } from 'lodash';
import styles from './styles.scss';
class LimitSelect extends React.Component {
componentWillMount() {
const id = _.uniqueId();
this.setState({ id });
}
/**
* Return the list of default values to populate the select options
*
* @returns {number[]}
*/
getOptionsValues() {
return [10, 20, 50, 100];
}
render() {
return (
<form className="form-inline">
<div className={styles.selectWrapper}>
<select
onChange={this.props.onChangeLimit}
className={`form-control ${styles.select}`}
id={this.state.id}
value={this.props.limit}
>
{map(this.getOptionsValues(), (optionValue, key) => <option value={optionValue} key={key}>{optionValue}</option>)}
</select>
</div>
<label className={styles.label} htmlFor={this.state.id}>
<FormattedMessage id="content-manager.components.LimitSelect.itemsPerPage" />
</label>
</form>
);
}
}
LimitSelect.propTypes = {
limit: PropTypes.number.isRequired,
onChangeLimit: PropTypes.func.isRequired,
};
export default LimitSelect;

View File

@ -1,6 +0,0 @@
{
"itemsPerPage": {
"id": "contentManager.components.LimitSelect.itemsPerPage",
"defaultMessage": "Items per page"
}
}

View File

@ -1,28 +0,0 @@
.selectWrapper {
display: inline-block;
}
.select {
height: 3.2rem !important;
min-width: 5.4rem;
padding-top: 0rem;
padding-left: 1rem;
padding-right: 3rem;
background-position: right -1px center;
background-repeat: no-repeat;
background-image: url('./background_input.svg');
border: 1px solid #E3E9F3;
border-radius: 0.25rem;
line-height: 3.2rem;
font-size: 1.3rem;
font-family: 'Lato' !important;
-moz-appearance: none;
-webkit-appearance: none;
}
.label {
color: #465373;
margin-left: 1rem;
}

View File

@ -1,175 +0,0 @@
/**
*
* Pagination
*
*/
import React from 'react';
import { map } from 'lodash';
import PropTypes from 'prop-types';
import styles from './styles.scss';
class Pagination extends React.Component {
getLastPageNumber = () => {
return Math.ceil(this.props.count / this.props.limit);
}
handleDotsClick = (e) => {
e.preventDefault();
}
handlePreviousPageClick = (e) => {
e.preventDefault();
if (!this.isFirstPage()) {
this.props.onChangePage(this.props.currentPage - 1);
}
}
handleNextPageClick = (e) => {
e.preventDefault();
if (!this.isLastPage()) {
this.props.onChangePage(this.props.currentPage + 1);
}
}
handleFirstPageClick = (e) => {
e.preventDefault();
this.props.onChangePage(1);
}
handleLastPageClick = (e) => {
e.preventDefault();
this.props.onChangePage(this.getLastPageNumber());
}
isFirstPage = () => {
return this.props.currentPage === 1;
}
isLastPage = () => {
return this.props.currentPage === this.getLastPageNumber();
}
needAfterLinksDots = () => {
return this.props.currentPage < this.getLastPageNumber() - 1;
}
needPreviousLinksDots = () => {
return this.props.currentPage > 3;
}
renderLinks = () => {
// Init variables
const linksOptions = [];
// Add active page link
linksOptions.push({
value: this.props.currentPage,
isActive: true,
handleClick: e => {
e.preventDefault();
},
});
// Add previous page link
if (!this.isFirstPage()) {
linksOptions.unshift({
value: this.props.currentPage - 1,
isActive: false,
handleClick: this.handlePreviousPageClick,
});
}
// Add next page link
if (!this.isLastPage() && this.props.count > this.props.limit) {
linksOptions.push({
value: this.props.currentPage + 1,
isActive: false,
handleClick: this.handleNextPageClick,
});
}
if (this.needPreviousLinksDots()) {
linksOptions.unshift({
value: 1,
isActive: false,
handleClick: this.handleFirstPageClick,
});
}
if (this.needAfterLinksDots()) {
linksOptions.push({
value: this.getLastPageNumber(),
isActive: false,
handleClick: this.handleLastPageClick,
});
}
// Generate links
return (
map(linksOptions, (linksOption, key) => (
<li
className={`${linksOption.isActive && styles.navLiActive}`}
key={key}
>
<a href="" disabled={linksOption.isActive} onClick={linksOption.handleClick}>
{linksOption.value}
</a>
</li>
))
);
}
render() {
return (
<div className={styles.pagination}>
<div>
<a
href=""
className={`
${styles.paginationNavigator}
${this.isFirstPage() && styles.paginationNavigatorDisabled}
`}
onClick={this.handlePreviousPageClick}
disabled={this.isFirstPage()}
>
<i className="fa fa-angle-left" aria-hidden="true"></i>
</a>
<nav className={styles.nav}>
<ul className={styles.navUl}>
{this.renderLinks()}
</ul>
</nav>
<a
href=""
className={`
${styles.paginationNavigator}
${this.isLastPage() && styles.paginationNavigatorDisabled}
`}
onClick={this.handleNextPageClick}
disabled={this.isLastPage()}
>
<i className="fa fa-angle-right" aria-hidden="true"></i>
</a>
</div>
</div>
);
}
}
Pagination.propTypes = {
count: PropTypes.oneOfType([
PropTypes.number,
PropTypes.bool,
]).isRequired,
currentPage: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired,
onChangePage: PropTypes.func.isRequired,
};
export default Pagination;

View File

@ -1,127 +0,0 @@
.pagination {
width: 100%;
display: flex;
justify-content: flex-end;
>div{
display: inline-flex;
flex-direction: row;
min-width: 120px;
height: 32px;
background: #ffffff;
border-radius: 3px;
border: 1px solid #E2E8F3;
overflow: hidden;
box-shadow: 0 2px 4px #E3E9F3;
}
}
.paginationNavigator {
position: relative;
width: 36px;
text-align: center;
line-height: 30px;
font-size: 2rem;
&:first-of-type{
margin-right: 10px;
&:after{
position: absolute;
content: '';
top: 3px; bottom: 3px; right: 0;
width: 1px;
background: #F1F1F2;
}
}
&:last-of-type{
margin-left: 10px;
&:before{
position: absolute;
content: '';
top: 3px; bottom: 3px; left: 0;
width: 1px;
background: #F1F1F2;
}
}
i{
color: #97999E;
}
&[disabled] {
i{
opacity: 0.3;
}
}
}
.nav {
min-width: 48px;
ul{
display: flex;
flex-direction: row;
justify-content: center;
height: 100%;
margin: 0 -5px;
padding: 0;
}
li{
position: relative;
min-width: 15px;
margin: 0 5px !important;
text-align: center;
line-height: 32px;
color: #333740;
a {
color: #333740;
font-size: 1.3rem;
&:hover{
&:after{
position: absolute;
content: '';
bottom: 0; left: 0;
width: 100%;
height: 2px;
background: #1C5DE7;
}
}
&:hover,
&:visited,
&:focus,
&:active {
text-decoration: none;
color: #333740;
}
}
}
}
.navUl {
display: flex;
flex-direction: row;
justify-content: center;
margin: 0;
padding: 0;
}
.navLiActive {
font-weight: 800;
&:after{
position: absolute;
content: '';
bottom: 0; left: 0;
width: 100%;
height: 2px;
background: #1C5DE7;
}
}

View File

@ -1,50 +0,0 @@
/**
*
* TableFooter
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import LimitSelect from '../LimitSelect';
import Pagination from '../Pagination';
import styles from './styles.scss';
class TableFooter extends React.Component {
render() {
return (
<div className={`row ${styles.tableFooter}`}>
<div className="col-lg-6">
<LimitSelect
onChangeLimit={this.props.onChangeLimit}
limit={this.props.limit}
/>
</div>
<div className="col-lg-6">
<Pagination
limit={this.props.limit}
currentPage={this.props.currentPage}
onChangePage={this.props.onChangePage}
count={this.props.count}
/>
</div>
</div>
);
}
}
TableFooter.propTypes = {
count: PropTypes.oneOfType([
PropTypes.number,
PropTypes.bool,
]).isRequired,
currentPage: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired,
onChangeLimit: PropTypes.func.isRequired,
onChangePage: PropTypes.func.isRequired,
};
export default TableFooter;

View File

@ -1,3 +0,0 @@
.tableFooter {
margin-top: 2.8rem;
}

View File

@ -17,15 +17,12 @@ import injectSaga from 'utils/injectSaga';
import getQueryParameters from 'utils/getQueryParameters';
import Home from 'containers/Home';
// import Edit from 'containers/Edit';
import EditPage from 'containers/EditPage';
// import List from 'containers/List';
import ListPage from 'containers/ListPage';
import EmptyAttributesView from 'components/EmptyAttributesView';
import {
emptyStore,
// getModelEntries,
loadModels,
} from './actions';
import { makeSelectLoading, makeSelectModels, makeSelectModelEntries } from './selectors';
@ -35,24 +32,8 @@ import saga from './sagas';
class App extends React.Component {
componentDidMount() {
this.props.loadModels();
// NOTE: I'm commenting this part of code since I'm not sure why it is needed
// const modelName = this.props.location.pathname.split('/')[3];
// if (modelName) {
// this.props.getModelEntries(modelName, getQueryParameters(this.props.location.search, 'source'));
// }
}
// NOTE: I'm commenting this part of code since I'm not sure why it is needed
// componentDidUpdate(prevProps) {
// const currentModelName = this.props.location.pathname.split('/')[3];
//
// if (prevProps.location.pathname !== this.props.location.pathname && currentModelName) {
// this.props.getModelEntries(currentModelName, getQueryParameters(this.props.location.search, 'source'));
// }
// }
componentWillUnmount() {
this.props.emptyStore();
}
@ -76,10 +57,6 @@ class App extends React.Component {
<Switch>
<Route path="/plugins/content-manager/:slug/:id" component={EditPage} />
<Route path="/plugins/content-manager/:slug" component={ListPage} />
{/* Note: I'm commenting this lines in case we need to rollback to the previous containers
<Route path="/plugins/content-manager/:slug/:id" component={Edit} />
<Route path="/plugins/content-manager/:slug" component={List} />
*/}
<Route path="/plugins/content-manager" component={Home} />
</Switch>
</div>
@ -93,7 +70,6 @@ App.contextTypes = {
App.propTypes = {
emptyStore: PropTypes.func.isRequired,
// getModelEntries: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
loading: PropTypes.bool.isRequired,
loadModels: PropTypes.func.isRequired,

View File

@ -1,163 +0,0 @@
/*
*
* Edit actions
*
*/
import { get } from 'lodash';
import { getValidationsFromForm } from '../../utils/formValidations';
import {
CANCEL_CHANGES,
DELETE_RECORD,
DELETE_RECORD_ERROR,
DELETE_RECORD_SUCCESS,
EDIT_RECORD,
EDIT_RECORD_ERROR,
EDIT_RECORD_SUCCESS,
SET_CURRENT_MODEL_NAME,
SET_IS_CREATING,
SET_INITIAL_STATE,
LOAD_RECORD,
LOAD_RECORD_SUCCESS,
SET_RECORD_ATTRIBUTE,
TOGGLE_NULL,
RESET_EDIT_SUCCESS,
SET_FORM_VALIDATIONS,
SET_FORM,
SET_FORM_ERRORS,
} from './constants';
export function cancelChanges() {
return {
type: CANCEL_CHANGES,
};
}
export function deleteRecord(id, modelName, source) {
return {
type: DELETE_RECORD,
id,
modelName,
source,
};
}
export function editRecord(source) {
return {
type: EDIT_RECORD,
source,
};
}
export function loadRecord(id, source) {
return {
type: LOAD_RECORD,
id,
source,
};
}
export function recordDeleted(id) {
return {
type: DELETE_RECORD_SUCCESS,
id,
};
}
export function recordDeleteError() {
return {
type: DELETE_RECORD_ERROR,
};
}
export function recordEdited() {
return {
type: EDIT_RECORD_SUCCESS,
};
}
export function recordEditError() {
return {
type: EDIT_RECORD_ERROR,
};
}
export function recordLoaded(record) {
return {
type: LOAD_RECORD_SUCCESS,
record,
};
}
export function resetEditSuccess() {
return {
type: RESET_EDIT_SUCCESS,
};
}
export function setCurrentModelName(currentModelName) {
return {
type: SET_CURRENT_MODEL_NAME,
currentModelName,
};
}
export function setForm(data) {
const form = [];
Object.keys(data).map(attr => {
form.push([attr, '']);
});
return {
type: SET_FORM,
form,
};
}
export function setFormErrors(formErrors) {
return {
type: SET_FORM_ERRORS,
formErrors,
};
}
export function setFormValidations(data) {
const form = Object.keys(data).map(attr => {
return { name: attr, validations: get(data, attr) || {} };
});
const formValidations = getValidationsFromForm(form, []);
return {
type: SET_FORM_VALIDATIONS,
formValidations,
};
}
export function setInitialState() {
return {
type: SET_INITIAL_STATE,
};
}
export function setIsCreating() {
return {
type: SET_IS_CREATING,
};
}
export function setRecordAttribute(key, value) {
return {
type: SET_RECORD_ATTRIBUTE,
key,
value,
};
}
export function toggleNull() {
return {
type: TOGGLE_NULL,
};
}

View File

@ -1,29 +0,0 @@
/*
*
* Edit constants
*
*/
export const SET_INITIAL_STATE = 'app/Edit/SET_INITIAL_STATE';
export const SET_CURRENT_MODEL_NAME = 'app/Edit/SET_CURRENT_MODEL_NAME';
export const SET_IS_CREATING = 'app/Edit/SET_IS_CREATING';
export const SET_FORM_VALIDATIONS = 'app/Edit/SET_FORM_VALIDATIONS';
export const SET_FORM = 'app/Edit/SET_FORM';
export const SET_FORM_ERRORS = 'app/Edit/SET_FORM_ERRORS';
export const LOAD_RECORD = 'app/Edit/LOAD_RECORD';
export const LOAD_RECORD_SUCCESS = 'app/Edit/LOAD_RECORD_SUCCESS';
export const SET_RECORD_ATTRIBUTE = 'app/Edit/SET_RECORD_ATTRIBUTE';
export const EDIT_RECORD = 'app/Edit/EDIT_RECORD';
export const EDIT_RECORD_SUCCESS = 'app/Edit/EDIT_RECORD_SUCCESS';
export const EDIT_RECORD_ERROR = 'app/Edit/EDIT_RECORD_ERROR';
export const DELETE_RECORD = 'app/Edit/DELETE_RECORD';
export const DELETE_RECORD_SUCCESS = 'app/Edit/DELETE_RECORD_SUCCESS';
export const DELETE_RECORD_ERROR = 'app/Edit/DELETE_RECORD_ERROR';
export const CANCEL_CHANGES = 'app/Edit/CANCEL_CHANGES';
export const TOGGLE_NULL = 'app/Edit/TOGGLE_NULL';
export const RESET_EDIT_SUCCESS = 'app/Edit/RESET_EDIT_SUCCESS';

View File

@ -1,356 +0,0 @@
/*
*
* Edit
*
*/
// Dependencies.
import React from 'react';
import moment from 'moment';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { createStructuredSelector } from 'reselect';
import PropTypes from 'prop-types';
import {
get,
includes,
isObject,
isEmpty,
map,
replace,
toNumber,
toString,
} from 'lodash';
import { router } from 'app';
// Components.
import BackHeader from 'components/BackHeader';
import EditForm from 'components/EditForm';
import EditFormRelations from 'components/EditFormRelations';
import PluginHeader from 'components/PluginHeader';
// Selectors.
import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors';
// Utils.
import getQueryParameters from 'utils/getQueryParameters';
import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga';
import templateObject from 'utils/templateObject';
import { checkFormValidity } from '../../utils/formValidations';
import { bindLayout } from '../../utils/bindLayout';
// Layout
import layout from '../../../../config/layout';
// Styles.
import styles from './styles.scss';
// Actions.
import {
setInitialState,
setCurrentModelName,
setIsCreating,
loadRecord,
setRecordAttribute,
editRecord,
toggleNull,
cancelChanges,
setFormValidations,
setForm,
setFormErrors,
recordEdited,
resetEditSuccess,
} from './actions';
// Selectors.
import {
makeSelectRecord,
makeSelectLoading,
makeSelectCurrentModelName,
makeSelectEditing,
makeSelectDeleting,
makeSelectIsCreating,
makeSelectIsRelationComponentNull,
makeSelectForm,
makeSelectFormValidations,
makeSelectFormErrors,
makeSelectDidCheckErrors,
makeSelectEditSuccess,
} from './selectors';
import reducer from './reducer';
import saga from './sagas';
export class Edit extends React.Component {
constructor(props) {
super(props);
this.pluginHeaderActions = [
{
label: 'content-manager.containers.Edit.reset',
kind: 'secondary',
onClick: this.props.cancelChanges,
type: 'button',
},
{
kind: 'primary',
label: this.props.editing ? 'content-manager.containers.Edit.editing' : 'content-manager.containers.Edit.submit',
onClick: this.handleSubmit,
disabled: this.props.editing,
type: 'submit',
},
];
this.source = getQueryParameters(this.props.location.search, 'source');
this.layout = bindLayout.call(this, layout);
}
componentDidMount() {
const attributes =
get(this.props.models, ['models', this.props.match.params.slug.toLowerCase(), 'attributes']) ||
get(this.props.models, ['plugins', this.source, 'models', this.props.match.params.slug.toLowerCase(), 'attributes']);
if (this.source) {
this.layout = bindLayout.call(this, get(this.context.plugins.toJS(), `${this.source}.layout`, layout));
}
this.props.setInitialState();
this.props.setCurrentModelName(this.props.match.params.slug.toLowerCase());
this.props.setFormValidations(attributes);
this.props.setForm(attributes);
// Detect that the current route is the `create` route or not
if (this.props.match.params.id === 'create') {
this.props.setIsCreating();
} else {
this.props.loadRecord(this.props.match.params.id, this.source);
}
}
componentWillReceiveProps(nextProps) {
if (this.props.editSuccess !== nextProps.editSuccess) {
if (!isEmpty(this.props.location.search) && includes(this.props.location.search, '?redirectUrl')) {
const redirectUrl = this.props.location.search.split('?redirectUrl=')[1];
router.push({
pathname: redirectUrl.split('?')[0],
search: redirectUrl.split('?')[1],
});
} else {
router.push({
pathname: replace(this.props.location.pathname, '/create', ''),
search: `?source=${this.source}`,
});
}
}
}
componentWillUnmount() {
this.props.recordEdited();
this.props.resetEditSuccess();
this.props.setInitialState();
}
handleChange = (e) => {
const currentSchema = this.source !== 'content-manager' ? get(this.props.schema, ['plugins', this.source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
let formattedValue = e.target.value;
if (isObject(e.target.value) && e.target.value._isAMomentObject === true) {
formattedValue = moment(e.target.value, 'YYYY-MM-DD HH:mm:ss').format();
} else if (['float', 'integer', 'biginteger', 'decimal'].indexOf(currentSchema.fields[e.target.name].type) !== -1) {
formattedValue = toNumber(e.target.value);
}
this.props.setRecordAttribute(e.target.name, formattedValue);
}
handleSubmit = (e) => {
e.preventDefault();
const form = this.props.form.toJS();
map(this.props.record.toJS(), (value, key) => form[key] = value);
const formErrors = checkFormValidity(form, this.props.formValidations.toJS());
if (isEmpty(formErrors)) {
this.props.editRecord(this.source);
} else {
this.props.setFormErrors(formErrors);
}
}
render() {
if (this.props.loading || !this.props.schema || !this.props.currentModelName) {
return <p>Loading...</p>;
}
const currentModel = get(this.props.models, ['models', this.props.currentModelName]) || get(this.props.models, ['plugins', this.source, 'models', this.props.currentModelName]);
// Plugin header config
const primaryKey = currentModel.primaryKey;
const mainField = get(currentModel, 'info.mainField') || primaryKey;
const pluginHeaderTitle = this.props.isCreating ? 'New entry' : templateObject({ mainField }, this.props.record.toJS()).mainField;
const pluginHeaderDescription = this.props.isCreating ? 'New entry' : `#${this.props.record && this.props.record.get(primaryKey)}`;
return (
<div>
<BackHeader onClick={() => router.goBack()} />
<div className={`container-fluid ${styles.containerFluid}`}>
<PluginHeader
title={{
id: toString(pluginHeaderTitle),
}}
description={{
id: 'plugin-content-manager-description',
defaultMessage: `${pluginHeaderDescription}`,
}}
actions={this.pluginHeaderActions}
fullWidth={this.props.isRelationComponentNull}
/>
<div className='row'>
<div className={this.props.isRelationComponentNull ? `col-lg-12` : `col-lg-9`}>
<div className={`${styles.main_wrapper}`}>
<EditForm
record={this.props.record}
currentModelName={this.props.currentModelName}
schema={this.props.schema}
setRecordAttribute={this.props.setRecordAttribute}
onChange={this.handleChange}
onSubmit={this.handleSubmit}
editing={this.props.editing}
formErrors={this.props.formErrors.toJS()}
didCheckErrors={this.props.didCheckErrors}
formValidations={this.props.formValidations.toJS()}
layout={this.layout}
location={this.props.location}
/>
</div>
</div>
<div className={`col-lg-3 ${this.props.isRelationComponentNull ? 'hidden-xl-down' : ''}`}>
<div className={styles.sub_wrapper}>
<EditFormRelations
currentModelName={this.props.currentModelName}
record={this.props.record}
schema={this.props.schema}
setRecordAttribute={this.props.setRecordAttribute}
isNull={this.props.isRelationComponentNull}
toggleNull={this.props.toggleNull}
location={this.props.location}
/>
</div>
</div>
</div>
</div>
</div>
);
}
}
Edit.contextTypes = {
plugins: PropTypes.object,
updatePlugin: PropTypes.func,
};
/* eslint-disable react/require-default-props */
Edit.propTypes = {
cancelChanges: PropTypes.func.isRequired,
currentModelName: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
]).isRequired,
didCheckErrors: PropTypes.bool.isRequired,
editing: PropTypes.bool.isRequired,
editRecord: PropTypes.func.isRequired,
editSuccess: PropTypes.bool.isRequired,
form: PropTypes.object.isRequired,
formErrors: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
]),
formValidations: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
]),
isCreating: PropTypes.bool.isRequired,
isRelationComponentNull: PropTypes.bool.isRequired,
loading: PropTypes.bool.isRequired,
loadRecord: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
match: PropTypes.shape({
params: PropTypes.shape({
id: PropTypes.string,
slug: PropTypes.string,
}),
}).isRequired,
models: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,
]).isRequired,
record: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,
]).isRequired,
recordEdited: PropTypes.func,
resetEditSuccess: PropTypes.func,
schema: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,
]).isRequired,
setCurrentModelName: PropTypes.func.isRequired,
setForm: PropTypes.func.isRequired,
setFormErrors: PropTypes.func.isRequired,
setFormValidations: PropTypes.func.isRequired,
setInitialState: PropTypes.func.isRequired,
setIsCreating: PropTypes.func.isRequired,
setRecordAttribute: PropTypes.func.isRequired,
toggleNull: PropTypes.func.isRequired,
};
const mapStateToProps = createStructuredSelector({
record: makeSelectRecord(),
loading: makeSelectLoading(),
currentModelName: makeSelectCurrentModelName(),
editing: makeSelectEditing(),
deleting: makeSelectDeleting(),
isCreating: makeSelectIsCreating(),
schema: makeSelectSchema(),
models: makeSelectModels(),
isRelationComponentNull: makeSelectIsRelationComponentNull(),
form: makeSelectForm(),
formValidations: makeSelectFormValidations(),
formErrors: makeSelectFormErrors(),
didCheckErrors: makeSelectDidCheckErrors(),
editSuccess: makeSelectEditSuccess(),
});
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
setInitialState,
setCurrentModelName,
setIsCreating,
loadRecord,
setRecordAttribute,
editRecord,
toggleNull,
cancelChanges,
setFormValidations,
setForm,
setFormErrors,
recordEdited,
resetEditSuccess,
},
dispatch
);
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'edit', reducer });
const withSaga = injectSaga({ key: 'edit', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(Edit);

View File

@ -1,18 +0,0 @@
{
"submit": {
"id": "contentManager.containers.Edit.submit",
"defaultMessage": "Submit"
},
"editing": {
"id": "contentManager.containers.Edit.editing",
"defaultMessage": "Editing..."
},
"delete": {
"id": "contentManager.containers.Edit.delete",
"defaultMessage": "Delete"
},
"cancel": {
"id": "contentManager.containers.Edit.reset",
"defaultMessage": "Cancel"
}
}

View File

@ -1,106 +0,0 @@
/*
*
* Edit reducer
*
*/
import { fromJS, Map, List } from 'immutable';
import {
SET_INITIAL_STATE,
SET_CURRENT_MODEL_NAME,
SET_IS_CREATING,
LOAD_RECORD,
LOAD_RECORD_SUCCESS,
SET_RECORD_ATTRIBUTE,
EDIT_RECORD,
EDIT_RECORD_SUCCESS,
EDIT_RECORD_ERROR,
DELETE_RECORD,
DELETE_RECORD_SUCCESS,
DELETE_RECORD_ERROR,
TOGGLE_NULL,
CANCEL_CHANGES,
RESET_EDIT_SUCCESS,
SET_FORM_VALIDATIONS,
SET_FORM,
SET_FORM_ERRORS,
} from './constants';
const initialState = fromJS({
currentModelName: '',
loading: false,
record: false,
initialRecord: {},
editing: false,
deleting: false,
isCreating: false,
isRelationComponentNull: false,
formValidations: List([]),
formErrors: List([]),
form: Map({}),
didCheckErrors: false,
editSuccess: false,
});
function editReducer(state = initialState, action) {
switch (action.type) {
case SET_INITIAL_STATE:
return initialState;
case SET_CURRENT_MODEL_NAME:
return state.set('currentModelName', action.currentModelName);
case SET_IS_CREATING:
return state
.set('isCreating', true)
.set('loading', false)
.set('record', fromJS({}));
case LOAD_RECORD:
return state
.set('loading', true)
.set('model', action.model)
.set('id', action.id);
case LOAD_RECORD_SUCCESS:
return state
.set('loading', false)
.set('record', fromJS(action.record))
.set('initialRecord', fromJS(action.record));
case SET_RECORD_ATTRIBUTE:
return state
.setIn(['record', action.key], fromJS(action.value));
case EDIT_RECORD:
return state.set('editing', true);
case EDIT_RECORD_SUCCESS:
return state
.set('editSuccess', true)
.set('editing', false);
case EDIT_RECORD_ERROR:
return state.set('editing', false);
case DELETE_RECORD:
return state.set('deleting', true);
case DELETE_RECORD_SUCCESS:
return state.set('deleting', false);
case DELETE_RECORD_ERROR:
return state.set('deleting', false);
case TOGGLE_NULL:
return state.set('isRelationComponentNull', true);
case CANCEL_CHANGES:
return state
.set('formErrors', List([]))
.set('record', state.get('initialRecord'));
case SET_FORM_VALIDATIONS:
return state
.set('formValidations', List(action.formValidations));
case SET_FORM:
return state.set('form', Map(action.form));
case SET_FORM_ERRORS:
return state
.set('formErrors', List(action.formErrors))
.set('didCheckErrors', !state.get('didCheckErrors'));
case RESET_EDIT_SUCCESS:
return state.set('editSuccess', false);
default:
return state;
}
}
export default editReducer;

View File

@ -1,157 +0,0 @@
import { LOCATION_CHANGE } from 'react-router-redux';
import { get, isArray } from 'lodash';
import { call, cancel, fork, put, take, select, takeLatest } from 'redux-saga/effects';
import request from 'utils/request';
import cleanData from 'utils/cleanData';
import { router } from 'app';
import { decreaseCount } from 'containers/List/actions';
import {
recordLoaded,
recordEdited,
recordEditError,
recordDeleted,
recordDeleteError,
setFormErrors,
} from './actions';
import { LOAD_RECORD, EDIT_RECORD, DELETE_RECORD } from './constants';
import {
makeSelectCurrentModelName,
makeSelectRecord,
makeSelectIsCreating,
} from './selectors';
export function* getRecord(action) {
const currentModelName = yield select(makeSelectCurrentModelName());
const params = {};
if (action.source !== undefined) {
params.source = action.source;
}
try {
const requestUrl = `/content-manager/explorer/${currentModelName}/${action.id}`;
// Call our request helper (see 'utils/request')
const response = yield request(requestUrl, {
method: 'GET',
params,
});
yield put(recordLoaded(response));
} catch (err) {
strapi.notification.error('content-manager.error.record.fetch');
}
}
export function* editRecord(action) {
const currentModelName = yield select(makeSelectCurrentModelName());
const record = yield select(makeSelectRecord());
const recordJSON = record.toJSON();
const recordCleaned = Object.keys(recordJSON).reduce((acc, current) => {
acc[current] = cleanData(recordJSON[current], 'value', 'id');
return acc;
}, {});
const isCreating = yield select(makeSelectIsCreating());
const id = isCreating ? '' : recordCleaned.id;
const params = {};
if (action.source !== undefined) {
params.source = action.source;
}
try {
const requestUrl = `/content-manager/explorer/${currentModelName}/${id}`;
// Call our request helper (see 'utils/request')
yield call(request, requestUrl, {
method: isCreating ? 'POST' : 'PUT',
body: recordCleaned,
params,
});
strapi.notification.success('content-manager.success.record.save');
yield put(recordEdited());
} catch (err) {
if (isArray(err.response.payload.message)) {
const errors = err.response.payload.message.reduce((acc, current) => {
const error = current.messages.reduce((acc, current) => {
acc.errorMessage = current.id;
return acc;
}, { id: 'components.Input.error.custom-error', errorMessage: '' });
acc.push(error);
return acc;
}, []);
const name = get(err.response.payload.message, ['0', 'messages', '0', 'field']);
yield put(setFormErrors([{ name, errors }]));
}
yield put(recordEditError());
strapi.notification.error(isCreating ? 'content-manager.error.record.create' : 'content-manager.error.record.update');
}
}
export function* deleteRecord({ id, modelName, source }) {
function* httpCall(id, modelName) {
try {
const requestUrl = `/content-manager/explorer/${modelName}/${id}`;
const params = {};
if (source !== undefined) {
params.source = source;
}
// Call our request helper (see 'utils/request')
yield call(request, requestUrl, {
method: 'DELETE',
params,
});
yield put(recordDeleted(id));
yield put(decreaseCount());
strapi.notification.success('content-manager.success.record.delete');
// Redirect to the list page.
router.push({
pathname: `/plugins/content-manager/${modelName}`,
search: `?source=${source}`,
});
} catch (err) {
yield put(recordDeleteError());
strapi.notification.error('content-manager.error.record.delete');
}
}
if (id && modelName) {
yield httpCall(id, modelName);
} else {
const currentModelName = yield select(makeSelectCurrentModelName());
const record = yield select(makeSelectRecord());
const recordJSON = record.toJSON();
yield httpCall(recordJSON.id, currentModelName);
}
}
export function* defaultSaga() {
const loadRecordWatcher = yield fork(takeLatest, LOAD_RECORD, getRecord);
const editRecordWatcher = yield fork(takeLatest, EDIT_RECORD, editRecord);
const deleteRecordWatcher = yield fork(takeLatest, DELETE_RECORD, deleteRecord);
// Suspend execution until location changes
yield take(LOCATION_CHANGE);
yield cancel(loadRecordWatcher);
yield cancel(editRecordWatcher);
yield cancel(deleteRecordWatcher);
}
// All sagas to be loaded
export default defaultSaga;

View File

@ -1,68 +0,0 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the edit state domain
*/
const selectEditDomain = () => state => state.get('edit');
/**
* Other specific selectors
*/
/**
* Default selector used by Edit
*/
const makeSelectRecord = () =>
createSelector(selectEditDomain(), substate => substate.get('record'));
const makeSelectLoading = () =>
createSelector(selectEditDomain(), substate => substate.get('loading'));
const makeSelectCurrentModelName = () =>
createSelector(selectEditDomain(), substate =>
substate.get('currentModelName')
);
const makeSelectEditing = () =>
createSelector(selectEditDomain(), substate => substate.get('editing'));
const makeSelectDeleting = () =>
createSelector(selectEditDomain(), substate => substate.get('deleting'));
const makeSelectIsCreating = () =>
createSelector(selectEditDomain(), substate => substate.get('isCreating'));
const makeSelectIsRelationComponentNull = () =>
createSelector(selectEditDomain(), substate => substate.get('isRelationComponentNull'));
const makeSelectForm = () =>
createSelector(selectEditDomain(), substate => substate.get('form'));
const makeSelectFormValidations = () =>
createSelector(selectEditDomain(), substate => substate.get('formValidations'));
const makeSelectFormErrors = () =>
createSelector(selectEditDomain(), substate => substate.get('formErrors'));
const makeSelectDidCheckErrors = () =>
createSelector(selectEditDomain(), substate => substate.get('didCheckErrors'));
const makeSelectEditSuccess = () =>
createSelector(selectEditDomain(), substate => substate.get('editSuccess'));
export default selectEditDomain;
export {
makeSelectRecord,
makeSelectLoading,
makeSelectCurrentModelName,
makeSelectEditing,
makeSelectDeleting,
makeSelectIsCreating,
makeSelectIsRelationComponentNull,
makeSelectForm,
makeSelectFormValidations,
makeSelectFormErrors,
makeSelectDidCheckErrors,
makeSelectEditSuccess,
};

View File

@ -1,17 +0,0 @@
.containerFluid { /* stylelint-disable */
padding: 18px 30px;
}
.main_wrapper{
background: #ffffff;
padding: 22px 10px;
border-radius: 2px;
box-shadow: 0 2px 4px #E3E9F3;
}
.sub_wrapper{
background: #ffffff;
padding: 0 25px 1px;
border-radius: 2px;
box-shadow: 0 2px 4px #E3E9F3;
}

View File

@ -1,95 +0,0 @@
/*
*
* List actions
*
*/
import pluralize from 'pluralize';
import {
DELETE_RECORD_SUCCESS,
} from '../Edit/constants';
import {
CHANGE_LIMIT,
CHANGE_PAGE,
CHANGE_SORT,
DECREASE_COUNT,
LOAD_COUNT,
LOAD_RECORDS,
LOADED_COUNT,
LOADED_RECORDS,
SET_CURRENT_MODEL_NAME,
} from './constants';
export function changeLimit(limit, source) {
return {
type: CHANGE_LIMIT,
limit: limit <= 0 ? 20 : limit,
source,
};
}
export function changePage(page, source) {
return {
type: CHANGE_PAGE,
page: page <= 0 ? 1 : page,
source,
};
}
export function changeSort(sort, source) {
return {
type: CHANGE_SORT,
sort,
source,
};
}
export function decreaseCount() {
return {
type: DECREASE_COUNT,
};
}
export function loadCount(source) {
return {
type: LOAD_COUNT,
source,
};
}
export function loadRecords(source) {
return {
type: LOAD_RECORDS,
source,
};
}
export function loadedCount(count) {
return {
type: LOADED_COUNT,
count,
};
}
export function loadedRecord(records) {
return {
type: LOADED_RECORDS,
records,
};
}
export function setCurrentModelName(modelName) {
return {
type: SET_CURRENT_MODEL_NAME,
modelName,
modelNamePluralized: pluralize(modelName),
};
}
export function recordDeleted() {
return {
type: DELETE_RECORD_SUCCESS,
};
}

View File

@ -1,20 +0,0 @@
/*
*
* List constants
*
*/
export const SET_CURRENT_MODEL_NAME = 'app/List/SET_CURRENT_MODEL_NAME';
export const LOAD_RECORDS = 'app/List/LOAD_RECORDS';
export const LOADED_RECORDS = 'app/List/LOADED_RECORDS';
export const LOAD_COUNT = 'app/List/LOAD_COUNT';
export const LOADED_COUNT = 'app/List/LOADED_COUNT';
export const CHANGE_PAGE = 'app/List/CHANGE_PAGE';
export const CHANGE_SORT = 'app/List/CHANGE_SORT';
export const CHANGE_LIMIT = 'app/List/CHANGE_LIMIT';
export const DELETE_RECORD = 'app/List/DELETE_RECORD';
export const DECREASE_COUNT = 'app/List/DECREASE_COUNT';

View File

@ -1,343 +0,0 @@
/*
*
* List
*
*/
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { createStructuredSelector } from 'reselect';
import PropTypes from 'prop-types';
import { isEmpty, isUndefined, map, get, toInteger } from 'lodash';
import { router } from 'app';
// Selectors.
import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors';
// Components.
import Table from 'components/Table';
import TableFooter from 'components/TableFooter';
import PluginHeader from 'components/PluginHeader';
import PopUpWarning from 'components/PopUpWarning';
import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga';
// Utils
import getQueryParameters from 'utils/getQueryParameters';
// Actions.
import {
deleteRecord,
} from '../Edit/actions';
// Styles.
import styles from './styles.scss';
// Actions.
import {
setCurrentModelName,
loadRecords,
loadCount,
changePage,
changeSort,
changeLimit,
} from './actions';
// Selectors.
import {
makeSelectRecords,
makeSelectCurrentModelName,
makeSelectCurrentModelNamePluralized,
makeSelectCount,
makeSelectCurrentPage,
makeSelectLimit,
makeSelectSort,
makeSelectLoadingCount,
} from './selectors';
import reducer from './reducer';
import saga from './sagas';
export class List extends React.Component {
constructor(props) {
super(props);
this.state = {
showWarning: false,
};
}
componentDidMount() {
// Init the view
this.init(this.props);
}
componentWillReceiveProps(nextProps) {
this.source = getQueryParameters(nextProps.location.search, 'source');
const locationChanged = nextProps.location.pathname !== this.props.location.pathname;
if (locationChanged) {
this.init(nextProps);
}
if (!isEmpty(nextProps.location.search) && this.props.location.search !== nextProps.location.search) {
this.props.loadRecords(getQueryParameters(nextProps.location.search, 'source'));
}
}
init(props) {
const source = getQueryParameters(props.location.search, 'source');
const slug = props.match.params.slug;
// Set current model name
this.props.setCurrentModelName(slug.toLowerCase());
const sort = (isEmpty(props.location.search) ?
get(this.props.models, ['models', slug.toLowerCase(), 'primaryKey']) || get(this.props.models.plugins, [source, 'models', slug.toLowerCase(), 'primaryKey']) :
getQueryParameters('sort')) || 'id';
if (!isEmpty(props.location.search)) {
this.props.changePage(toInteger(getQueryParameters(props.location.search, 'page')), source);
this.props.changeLimit(toInteger(getQueryParameters(props.location.search, 'limit')), source);
}
this.props.changeSort(sort, source);
// Load records
this.props.loadRecords(source);
// Get the records count
this.props.loadCount(source);
// Define the `create` route url
this.addRoute = `${this.props.match.path.replace(':slug', slug)}/create`;
}
handleChangeLimit = ({ target }) => {
const source = getQueryParameters(this.props.location.search, 'source');
this.props.changeLimit(toInteger(target.value), source);
router.push({
pathname: this.props.location.pathname,
search: `?page=${this.props.currentPage}&limit=${target.value}&sort=${this.props.sort}&source=${source}`,
});
}
handleChangePage = (page) => {
const source = getQueryParameters(this.props.location.search, 'source');
router.push({
pathname: this.props.location.pathname,
search: `?page=${page}&limit=${this.props.limit}&sort=${this.props.sort}&source=${source}`,
});
this.props.changePage(page, source);
}
handleChangeSort = (sort) => {
const source = getQueryParameters(this.props.location.search, 'source');
router.push({
pathname: this.props.location.pathname,
search: `?page=${this.props.currentPage}&limit=${this.props.limit}&sort=${sort}&source=${source}`,
});
this.props.changeSort(sort, source);
}
handleDelete = (e) => {
const source = getQueryParameters(this.props.location.search, 'source');
e.preventDefault();
e.stopPropagation();
this.props.deleteRecord(this.state.target, this.props.currentModelName, source);
this.setState({ showWarning: false });
}
toggleModalWarning = (e) => {
if (!isUndefined(e)) {
e.preventDefault();
e.stopPropagation();
this.setState({
target: e.target.id,
});
}
this.setState({ showWarning: !this.state.showWarning });
}
render() {
const source = getQueryParameters(this.props.location.search, 'source');
// Detect current model structure from models list
const currentModel = get(this.props.models, ['models', this.props.currentModelName]) || get(this.props.models, ['plugins', source, 'models', this.props.currentModelName]);
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
if (!this.props.currentModelName || !currentSchema) {
return <div />;
}
// Define table headers
const tableHeaders = map(currentSchema.list, (value) => ({
name: value,
label: currentSchema.fields[value].label,
type: currentSchema.fields[value].type,
}));
tableHeaders.splice(0, 0, { name: currentModel.primaryKey || 'id', label: 'Id', type: 'string' });
const content = (
<Table
records={this.props.records}
route={this.props.match}
routeParams={this.props.match.params}
headers={tableHeaders}
onChangeSort={this.handleChangeSort}
sort={this.props.sort}
history={this.props.history}
primaryKey={currentModel.primaryKey || 'id'}
handleDelete={this.toggleModalWarning}
redirectUrl={`?redirectUrl=/plugins/content-manager/${this.props.currentModelName.toLowerCase()}?page=${this.props.currentPage}&limit=${this.props.limit}&sort=${this.props.sort}&source=${source}`}
/>
);
// Plugin header config
const pluginHeaderTitle = currentSchema.label || 'Content Manager';
// Define plugin header actions
const pluginHeaderActions = [
{
label: 'content-manager.containers.List.addAnEntry',
labelValues: {
entity: pluginHeaderTitle,
},
kind: 'primaryAddShape',
onClick: () => this.context.router.history.push({
pathname: this.addRoute,
search: `?source=${source}`,
}),
},
];
return (
<div>
<div className={`container-fluid ${styles.containerFluid}`}>
<PluginHeader
title={{
id: pluginHeaderTitle,
}}
description={{
id: this.props.count > 1 ? 'content-manager.containers.List.pluginHeaderDescription' : 'content-manager.containers.List.pluginHeaderDescription.singular',
values: {
label: this.props.count,
},
}}
actions={pluginHeaderActions}
/>
<div className={`row ${styles.row}`}>
<div className='col-lg-12'>
{content}
<PopUpWarning
isOpen={this.state.showWarning}
toggleModal={this.toggleModalWarning}
content={{
title: 'content-manager.popUpWarning.title',
message: 'content-manager.popUpWarning.bodyMessage.contentType.delete',
cancel: 'content-manager.popUpWarning.button.cancel',
confirm: 'content-manager.popUpWarning.button.confirm',
}}
popUpWarningType={'danger'}
onConfirm={this.handleDelete}
/>
<TableFooter
limit={this.props.limit}
currentPage={this.props.currentPage}
onChangePage={this.handleChangePage}
count={this.props.count}
className="push-lg-right"
onChangeLimit={this.handleChangeLimit}
/>
</div>
</div>
</div>
</div>
);
}
}
List.contextTypes = {
router: PropTypes.object.isRequired,
};
List.propTypes = {
changeLimit: PropTypes.func.isRequired,
changePage: PropTypes.func.isRequired,
changeSort: PropTypes.func.isRequired,
count: PropTypes.oneOfType([
PropTypes.number,
PropTypes.bool,
]).isRequired,
currentModelName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]).isRequired,
currentPage: PropTypes.number.isRequired,
deleteRecord: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
limit: PropTypes.number.isRequired,
loadCount: PropTypes.func.isRequired,
loadRecords: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
models: PropTypes.oneOfType([
PropTypes.object,
PropTypes.bool,
]).isRequired,
records: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
]).isRequired,
// route: PropTypes.object.isRequired,
schema: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.object,
]).isRequired,
setCurrentModelName: PropTypes.func.isRequired,
sort: PropTypes.string.isRequired,
};
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
deleteRecord,
setCurrentModelName,
loadRecords,
loadCount,
changePage,
changeSort,
changeLimit,
},
dispatch
);
}
const mapStateToProps = createStructuredSelector({
records: makeSelectRecords(),
count: makeSelectCount(),
loadingCount: makeSelectLoadingCount(),
models: makeSelectModels(),
currentPage: makeSelectCurrentPage(),
limit: makeSelectLimit(),
sort: makeSelectSort(),
currentModelName: makeSelectCurrentModelName(),
currentModelNamePluralized: makeSelectCurrentModelNamePluralized(),
schema: makeSelectSchema(),
});
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'list', reducer });
const withSaga = injectSaga({ key: 'list', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(List);

View File

@ -1,10 +0,0 @@
{
"addAnEntry": {
"id": "contentManager.containers.List.addAnEntry",
"defaultMessage": "Add an entry"
},
"pluginHeaderDescription": {
"id": "contentManager.containers.List.pluginHeaderDescription",
"defaultMessage": "Manage your {label}"
}
}

View File

@ -1,78 +0,0 @@
/*
*
* List reducer
*
*/
// Dependencies.
import { fromJS } from 'immutable';
// Constants.
import {
DELETE_RECORD_SUCCESS,
} from '../Edit/constants';
import {
SET_CURRENT_MODEL_NAME,
LOAD_RECORDS,
LOADED_RECORDS,
LOAD_COUNT,
LOADED_COUNT,
CHANGE_PAGE,
CHANGE_SORT,
CHANGE_LIMIT,
DECREASE_COUNT,
} from './constants';
const initialState = fromJS({
currentModel: false,
currentModelName: false,
currentModelNamePluralized: false,
loadingRecords: true,
records: [],
loadingCount: true,
count: false,
currentPage: 1,
limit: 10,
sort: 'id',
initLimit: 10,
initSort: 'id',
});
function listReducer(state = initialState, action) {
switch (action.type) {
case DECREASE_COUNT:
return state.update('count', (value) => value - 1);
case SET_CURRENT_MODEL_NAME:
return state
.set('currentModelName', action.modelName)
.set('currentModelNamePluralized', action.modelNamePluralized);
case LOAD_RECORDS:
return state.set('loadingRecords', true);
case LOADED_RECORDS:
return state.set('loadingRecords', false).set('records', action.records);
case LOAD_COUNT:
return state.set('loadingCount', true);
case LOADED_COUNT:
return state.set('loadingCount', false).set('count', action.count);
case CHANGE_PAGE:
return state.set('currentPage', action.page);
case CHANGE_SORT:
return state.set('sort', action.sort);
case CHANGE_LIMIT:
return state.set('limit', action.limit);
case DELETE_RECORD_SUCCESS:
return state.set('records', state.get('records').filter(o => {
if (o._id) {
return o._id !== action.id;
}
return o.id !== action.id;
}));
default:
return state;
}
}
export default listReducer;

View File

@ -1,96 +0,0 @@
// Dependencies.
import { LOCATION_CHANGE } from 'react-router-redux';
import { put, select, fork, call, take, cancel, takeLatest } from 'redux-saga/effects';
// Utils.
import request from 'utils/request';
// Constants.
import { DELETE_RECORD } from '../Edit/constants';
// Sagas.
import { deleteRecord } from '../Edit/sagas';
// Actions.
import { loadedRecord, loadedCount } from './actions';
// Constants.
import { LOAD_RECORDS, LOAD_COUNT } from './constants';
// Selectors.
import {
makeSelectCurrentModelName,
makeSelectLimit,
makeSelectCurrentPage,
makeSelectSort,
} from './selectors';
export function* getRecords(action) {
const currentModel = yield select(makeSelectCurrentModelName());
const limit = yield select(makeSelectLimit());
const currentPage = yield select(makeSelectCurrentPage());
const sort = yield select(makeSelectSort());
// Calculate the number of values to be skip
const skip = (currentPage - 1) * limit;
// Init `params` object
const params = {
skip,
limit,
sort,
};
if (action.source !== undefined) {
params.source = action.source;
}
try {
const requestUrl = `/content-manager/explorer/${currentModel}`;
// Call our request helper (see 'utils/request')
const response = yield call(request, requestUrl, {
method: 'GET',
params,
});
yield put(loadedRecord(response));
} catch (err) {
strapi.notification.error('content-manager.error.records.fetch');
}
}
export function* getCount(action) {
const currentModel = yield select(makeSelectCurrentModelName());
const params = {};
if (action.source !== undefined) {
params.source = action.source;
}
try {
const response = yield call(request,`/content-manager/explorer/${currentModel}/count`, {
method: 'GET',
params,
});
yield put(loadedCount(response.count));
} catch (err) {
strapi.notification.error('content-manager.error.records.count');
}
}
// Individual exports for testing
export function* defaultSaga() {
const loadRecordsWatcher = yield fork(takeLatest, LOAD_RECORDS, getRecords);
const loudCountWatcher = yield fork(takeLatest, LOAD_COUNT, getCount);
const deleteRecordWatcher = yield fork(takeLatest, DELETE_RECORD, deleteRecord);
// Suspend execution until location changes
yield take(LOCATION_CHANGE);
yield cancel(loadRecordsWatcher);
yield cancel(loudCountWatcher);
yield cancel(deleteRecordWatcher);
}
// All sagas to be loaded
export default defaultSaga;

View File

@ -1,60 +0,0 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the list state domain
*/
const selectListDomain = () => state => state.get('list');
/**
* Other specific selectors
*/
/**
* Default selector used by List
*/
const makeSelectRecords = () =>
createSelector(selectListDomain(), substate => substate.get('records'));
const makeSelectLoadingRecords = () =>
createSelector(selectListDomain(), substate =>
substate.get('loadingRecords')
);
const makeSelectCount = () =>
createSelector(selectListDomain(), substate => substate.get('count'));
const makeSelectLoadingCount = () =>
createSelector(selectListDomain(), substate => substate.get('loadingCount'));
const makeSelectCurrentPage = () =>
createSelector(selectListDomain(), substate => substate.get('currentPage'));
const makeSelectLimit = () =>
createSelector(selectListDomain(), substate => substate.get('limit'));
const makeSelectSort = () =>
createSelector(selectListDomain(), substate => substate.get('sort'));
const makeSelectCurrentModelName = () =>
createSelector(selectListDomain(), substate =>
substate.get('currentModelName')
);
const makeSelectCurrentModelNamePluralized = () =>
createSelector(selectListDomain(), substate =>
substate.get('currentModelNamePluralized')
);
export {
selectListDomain,
makeSelectRecords,
makeSelectLoadingRecords,
makeSelectCount,
makeSelectLoadingCount,
makeSelectCurrentPage,
makeSelectLimit,
makeSelectSort,
makeSelectCurrentModelName,
makeSelectCurrentModelNamePluralized,
};

View File

@ -1,11 +0,0 @@
.containerFluid { /* stylelint-disable */
padding: 18px 30px;
.modal-content{
padding: 0;
}
}
.row{
padding-bottom: 36px;
}

View File

@ -84,7 +84,7 @@ class Li extends React.Component {
return <FileIcon key={key} fileType={item[value]} />;
}
if (value !== 'url' && value !== '') {
if (value !== '') {
return (
<div key={key} className={styles.truncate}>{item[value]}</div>
);

View File

@ -1,5 +0,0 @@
/*
*
* App actions
*
*/

View File

@ -1,5 +0,0 @@
/*
*
* App constants
*
*/

View File

@ -6,11 +6,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { Switch, Route } from 'react-router-dom';
import { bindActionCreators, compose } from 'redux';
// Utils
import { pluginId } from 'app';
@ -19,39 +15,15 @@ import { pluginId } from 'app';
import HomePage from 'containers/HomePage';
import NotFoundPage from 'containers/NotFoundPage';
class App extends React.Component {
render() {
return (
<div className={pluginId}>
<Switch>
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
<Route component={NotFoundPage} />
</Switch>
</div>
);
}
}
App.contextTypes = {
plugins: PropTypes.object,
router: PropTypes.object.isRequired,
updatePlugin: PropTypes.func,
};
App.propTypes = {};
export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{},
dispatch,
function App() {
return (
<div className={pluginId}>
<Switch>
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
<Route component={NotFoundPage} />
</Switch>
</div>
);
}
const mapStateToProps = createStructuredSelector({});
// Wrap the component to inject dispatch and state into it
const withConnect = connect(mapStateToProps, mapDispatchToProps);
export default compose(
withConnect,
)(App);
export default App;