mirror of
https://github.com/strapi/strapi.git
synced 2025-08-21 15:19:22 +00:00
Init content type creation
This commit is contained in:
parent
68ab68d428
commit
c52eb01b49
@ -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
|
||||
|
@ -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.",
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,5 @@
|
||||
function init(initialState) {
|
||||
return initialState;
|
||||
}
|
||||
|
||||
export default init;
|
@ -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 };
|
@ -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 };
|
@ -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;
|
||||
|
@ -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"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user