Init content type creation

This commit is contained in:
soupette 2019-11-14 17:34:31 +01:00 committed by Alexandre Bodin
parent 68ab68d428
commit c52eb01b49
9 changed files with 210 additions and 25 deletions

View File

@ -10,6 +10,7 @@ import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { bindActionCreators, compose } from 'redux';
import { Switch, Route } from 'react-router-dom';
import { injectIntl } from 'react-intl';
import { isEmpty } from 'lodash';
// Components from strapi-helper-plugin
import {
@ -145,6 +146,7 @@ export class Admin extends React.Component {
currentEnvironment={this.props.global.currentEnvironment}
disableGlobalOverlayBlocker={this.props.disableGlobalOverlayBlocker}
enableGlobalOverlayBlocker={this.props.enableGlobalOverlayBlocker}
formatMessage={this.props.intl.formatMessage}
plugins={this.props.global.plugins}
updatePlugin={this.props.updatePlugin}
>
@ -212,6 +214,9 @@ Admin.propTypes = {
showGlobalAppBlocker: PropTypes.bool,
strapiVersion: PropTypes.string,
}).isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func,
}),
location: PropTypes.object.isRequired,
setAppError: PropTypes.func.isRequired,
updatePlugin: PropTypes.func.isRequired,
@ -243,6 +248,7 @@ const withReducer = injectReducer({ key: 'admin', reducer });
const withSaga = injectSaga({ key: 'admin', saga });
export default compose(
injectIntl,
withReducer,
withSaga,
withConnect

View File

@ -128,6 +128,7 @@
"components.Input.error.validation.minSupMax": "Can't be superior",
"components.Input.error.validation.regex": "The value not match the regex.",
"components.Input.error.validation.required": "This value is required.",
"components.Input.error.validation.unique": "This value is already used.",
"components.InputSelect.option.placeholder": "Choose here",
"components.ListRow.empty": "There is no data to be shown.",
"components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.",

View File

@ -7,6 +7,7 @@ const errorsTrads = {
minLength: 'components.Input.error.validation.minLength',
regex: 'components.Input.error.validation.regex',
required: 'components.Input.error.validation.required',
unique: 'components.Input.error.validation.unique',
};
export default errorsTrads;

View File

@ -1,18 +1,20 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useReducer, useState } from 'react';
// import PropTypes from 'prop-types';
import { isEmpty } from 'lodash';
import {
ButtonModal,
HeaderModal,
HeaderModalTitle,
Modal,
ModalBody,
ModalFooter,
ModalForm,
getYupInnerErrors,
useGlobalContext,
} from 'strapi-helper-plugin';
import { Inputs } from '@buffetjs/custom';
import { useHistory, useLocation } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import { get, upperFirst } from 'lodash';
import { get, isEmpty, upperFirst } from 'lodash';
import pluginId from '../../pluginId';
import useQuery from '../../hooks/useQuery';
import useDataManager from '../../hooks/useDataManager';
@ -20,23 +22,28 @@ import ModalHeader from '../../components/ModalHeader';
import HeaderModalNavContainer from '../../components/HeaderModalNavContainer';
import HeaderNavLink from '../../components/HeaderNavLink';
import forms from './utils/forms';
import init from './init';
import reducer, { initialState } from './reducer';
const getTrad = id => `${pluginId}.${id}`;
const NAVLINKS = [{ id: 'base' }, { id: 'advanced' }];
const FormModal = () => {
const initialState = {
const initialStateData = {
actionType: null,
modalType: null,
settingType: null,
// uid: null,
};
const [state, setState] = useState(initialState);
const [state, setState] = useState(initialStateData);
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
const { push } = useHistory();
const { search } = useLocation();
const { formatMessage } = useGlobalContext();
const isOpen = !isEmpty(search);
const query = useQuery();
const { initialData } = useDataManager();
const { contentTypes, initialData } = useDataManager();
const { formErrors, modifiedData } = reducerState.toJS();
useEffect(() => {
if (isOpen) {
@ -54,9 +61,6 @@ const FormModal = () => {
? `modalForm.${state.modalType}.header-create`
: 'modalForm.header-edit';
const name = get(initialData, ['schema', 'name'], '');
const onClosed = () => {
setState(initialState);
};
const getNextSearch = nextTab => {
const newSearch = Object.keys(state).reduce((acc, current) => {
if (current !== 'settingType') {
@ -70,22 +74,43 @@ const FormModal = () => {
return newSearch;
};
const handleChange = ({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
keys: name.split('.'),
value,
});
};
const handleSubmit = async e => {
e.preventDefault();
try {
const schema = forms.contentType.schema(['admin', 'series', 'file']);
const schema = forms.contentType.schema(Object.keys(contentTypes));
await schema.validate({ name: 'admin' }, { abortEarly: false });
await schema.validate(modifiedData, { abortEarly: false });
} catch (err) {
const errors = getYupInnerErrors(err);
// TODO
console.log({ errors });
dispatch({
type: 'SET_ERRORS',
errors,
});
}
};
const handleToggle = () => {
push({ search: '' });
};
const onClosed = () => {
setState(initialStateData);
dispatch({
type: 'RESET_PROPS',
});
};
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
items: [],
}));
return (
<Modal isOpen={isOpen} onClosed={onClosed} onToggle={handleToggle}>
@ -123,7 +148,53 @@ const FormModal = () => {
</section>
</HeaderModal>
<form onSubmit={handleSubmit}>
<ModalForm>{/* <ModalBody>{renderForm()}</ModalBody> */}</ModalForm>
<ModalForm>
<ModalBody>
<div className="container-fluid">
{form(modifiedData).items.map((row, index) => {
return (
<div className="row" key={index}>
{row.map(input => {
const errorId = get(
formErrors,
[...input.name.split('.'), 'id'],
null
);
return (
<div
className={`col-${input.size || 6}`}
key={input.name}
>
<Inputs
value={get(modifiedData, input.name, '')}
{...input}
error={
isEmpty(errorId)
? null
: formatMessage({ id: errorId })
}
onChange={handleChange}
description={
get(input, 'description.id', null)
? formatMessage(input.description)
: input.description
}
label={
get(input, 'label.id', null)
? formatMessage(input.label)
: input.label
}
/>
</div>
);
})}
</div>
);
})}
</div>
</ModalBody>
</ModalForm>
<ModalFooter>
<section>
<ButtonModal

View File

@ -0,0 +1,5 @@
function init(initialState) {
return initialState;
}
export default init;

View File

@ -0,0 +1,25 @@
import { fromJS } from 'immutable';
const initialState = fromJS({
formErrors: {},
modifiedData: {},
});
const reducer = (state, action) => {
switch (action.type) {
case 'ON_CHANGE':
return state.updateIn(
['modifiedData', ...action.keys],
() => action.value
);
case 'RESET_PROPS':
return initialState;
case 'SET_ERRORS':
return state.update('formErrors', () => fromJS(action.errors));
default:
return state;
}
};
export default reducer;
export { initialState };

View File

@ -0,0 +1,12 @@
import slugify from '@sindresorhus/slugify';
const nameToSlug = name => slugify(name, { separator: '-' });
const createUid = name => {
const modelName = nameToSlug(name);
const uid = `application::${modelName}.${modelName}`;
return uid;
};
export { createUid, nameToSlug };

View File

@ -1,5 +1,7 @@
import * as yup from 'yup';
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
import pluginId from '../../../pluginId';
import { createUid, nameToSlug } from './createUid';
yup.addMethod(yup.mixed, 'defined', function() {
return this.test(
@ -10,10 +12,12 @@ yup.addMethod(yup.mixed, 'defined', function() {
});
yup.addMethod(yup.string, 'unique', function(message, allReadyTakenValues) {
console.log({ allReadyTakenValues });
return this.test('unique', message, function(string) {
console.log({ string });
return !allReadyTakenValues.includes(string);
if (!string) {
return false;
}
return !allReadyTakenValues.includes(createUid(string));
});
});
@ -23,21 +27,75 @@ const forms = {
return yup.object().shape({
name: yup
.string()
.required()
.unique('duplicate key', allReadyTakenValues),
.unique(errorsTrads.unique, allReadyTakenValues)
.required(errorsTrads.required),
collectionName: yup.string(),
});
},
form: {
base: {
base(data = {}) {
return {
items: [
[
{
autoFocus: true,
name: 'name',
type: 'string',
type: 'text',
label: {
id: `${pluginId}.contentType.displayName.label`,
},
validations: {
required: true,
},
},
{
description: {
id: `${pluginId}.contentType.UID.description`,
},
label: 'UID',
name: 'uid',
type: 'text',
readOnly: true,
disabled: true,
value: data.name ? nameToSlug(data.name) : '',
},
],
// Maybe for later
// [
// {
// name: 'repeatable',
// type: 'customBooleanContentType',
// value: true,
// title: 'Something',
// description: 'Cool',
// icon: 'multipleFiles',
// },
// ],
],
};
},
advanced() {
return {
items: [
[
{
autoFocus: true,
label: {
id: `${pluginId}.contentType.collectionName.label`,
},
description: {
id: `${pluginId}.contentType.collectionName.description`,
},
name: 'collectionName',
type: 'text',
validations: {},
},
],
],
};
},
},
},
};
export default forms;

View File

@ -220,8 +220,14 @@
"table.relations.title.plural": "including {number} relationships",
"table.relations.title.singular": "including {number} relationship",
"prompt.content.unsaved": "Are you sure you want to leave this content type? All your modifications will be lost.",
"modalForm.contentType.header-create": "Create a content type",
"modalForm.header-edit": "Edit {name}",
"modalForm.component.header-create": "Create a component",
"configurations": "configurations"
"configurations": "configurations",
"contentType.displayName.label": "Display name",
"contentType.collectionName.description": "Useful when the name of your Content Type and your table name differ",
"contentType.collectionName.label": "Collection name",
"contentType.UID.description": "The UID is used to generate the API routes and databases tables/collections"
}