diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx
index 111ac3ab27..4fd0d3aa7b 100644
--- a/ashes/src/components/content-types/content-type-form.jsx
+++ b/ashes/src/components/content-types/content-type-form.jsx
@@ -5,26 +5,41 @@ import _ from 'lodash';
import React, { Element } from 'react';
import { autobind } from 'core-decorators';
import { assoc } from 'sprout-data';
+import classNames from 'classnames';
import styles from '../object-page/object-details.css';
import ObjectDetails from '../object-page/object-details';
+import Modal from './modal';
+import Form from './form';
import { FormField } from '../forms';
import RadioButton from 'components/core/radio-button';
-import SelectCustomerGroups from '../customers-groups/select-groups';
import DiscountAttrs from './discount-attrs';
import offers from './offers';
import qualifiers from './qualifiers';
-import { setDiscountAttr } from 'paragons/promotion';
+import ContentBox from 'components/content-box/content-box';
+import { Button } from 'components/core/button';
+
+import {
+ addContentTypeObject,
+ updateContentTypeObject,
+ removeContentTypeObject
+} from 'paragons/content-type';
import { setObjectAttr, omitObjectAttr } from 'paragons/object';
import { customerGroups } from 'paragons/object-types';
const layout = require('./layout.json');
-export default class PromotionForm extends ObjectDetails {
+export default class ContentTypeForm extends ObjectDetails {
layout = layout;
+ state = {
+ tabs: {},
+ sections: {},
+ properties: {},
+ }
+
renderApplyType() {
const promotion = this.props.object;
return (
@@ -171,18 +186,289 @@ export default class PromotionForm extends ObjectDetails {
this.props.onUpdateObject(newPromotion);
}
- renderCustomers(): Element<*> {
- const promotion = this.props.object;
+ @autobind
+ setIsVisible(key, value) {
+ return id => {
+ this.setState({
+ [key]: {
+ ...this.state[key],
+ showModal: value,
+ id,
+ }
+ });
+ };
+ }
+
+ @autobind
+ onSave(key, id) {
+ return attributes => {
+ const { object: contentType } = this.props;
+ if (id > 0) {
+ this.props.onUpdateObject(updateContentTypeObject(contentType, key, id, attributes));
+ return id;
+ } else {
+ const object = addContentTypeObject(contentType, key, attributes);
+ this.props.onUpdateObject(object);
+ return _.last(object[key].allIds);
+ }
+ };
+ }
+
+ @autobind
+ onDelete(key, id) {
+ return () => {
+ const { object: contentType } = this.props;
+ this.props.onUpdateObject(removeContentTypeObject(contentType, key, id));
+ };
+ }
+
+ @autobind
+ onCancel(key) {
+ return this.setIsVisible(key, false);
+ }
+
+ formData(key: string) {
+ const schemes = {
+ tabs: {
+ fieldsToRender: ['title'],
+ schema: {
+ "type": "object",
+ "required": [
+ "title"
+ ],
+ "properties": {
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1
+ },
+ "custom-properties": {
+ "title": "Custom Properties can be added to this section",
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ sections: {
+ fieldsToRender: ['title', 'slug', 'custom-properties'],
+ schema: {
+ "type": "object",
+ "required": [
+ "title"
+ ],
+ "properties": {
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1
+ },
+ "custom-properties": {
+ "title": "Custom Properties can be added to this section",
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ properties: {
+ fieldsToRender: ['title', 'slug'],
+ schema: {
+ "type": "object",
+ "required": [
+ "title"
+ ],
+ "properties": {
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1
+ },
+ "custom-properties": {
+ "title": "Custom Properties can be added to this section",
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ };
+
+ return _.get(schemes, key, {});
+ }
+
+ modal({ key, title }): Element<*> {
+ const formData = this.formData(key);
+ const { object: contentType } = this.props;
+ const { id, showModal } = this.state[key];
+
+ return (
+
+ );
+ }
+
+ form({ key }): Element<*> {
+ const formData = this.formData(key);
+ const { object: contentType } = this.props;
+ const { id, showModal } = this.state[key];
+
+ if (!showModal) return null;
+
+ return (
+
+ );
+ }
+
+
+ column({ key, title, children, footer, emptyBody }): Element<*> {
+ const isEmpty = _.isEmpty(children);
+
+ const bodyClassName = classNames(
+ styles['column-body'],
+ {[styles['column-body-empty']]: isEmpty}
+ );
+
+ return (
+
+ {footer}
+
+ )}
+ indentContent={false}
+ >
+ {children}
+ {isEmpty && emptyBody}
+ {key === 'properties' ? null : this.modal({ key, title })}
+
+ );
+ }
+
+ renderColumns(): Element<*> {
+ const { object: contentType } = this.props;
return (
-
-
Customers
-
+
+ {this.column(
+ {
+ key: 'tabs',
+ title: 'Tab',
+ emptyBody: (
+
+ Add a tab!
+
+ ),
+ footer: (
+
+ ),
+ children: _.map(contentType.tabs.byId, (tab) =>
)
+ }
+ )}
+ {this.column(
+ {
+ key: 'sections',
+ title: 'Section',
+ emptyBody: (
+
+ Add a section!
+
+ ),
+ footer: (
+
+ ),
+ children: _.map(contentType.sections.byId, (section, id) => (
+
+ {section.attributes.title.v}
+
+
+ ))
+ }
+ )}
+ {this.column(
+ {
+ key: 'properties',
+ title: 'Properties',
+ emptyBody: (
+
+ Add a property!
+
+ ),
+ footer: (
+
+ ),
+ children: _.map(this.props.object.properties.byId, (property, id) => (
+
+ ))
+ }
+ )}
+ {this.column(
+ {
+ key: 'properties',
+ title: 'Property Settings',
+ footer: this.state.properties.showModal ? (
+
+ ) : null,
+ children: this.form(
+ {
+ key: 'properties',
+ }
+ )
+ }
+ )}
);
}
diff --git a/ashes/src/components/content-types/content-type-page.jsx b/ashes/src/components/content-types/content-type-page.jsx
index b9f9367b81..673f0a1e14 100644
--- a/ashes/src/components/content-types/content-type-page.jsx
+++ b/ashes/src/components/content-types/content-type-page.jsx
@@ -13,6 +13,102 @@ import { transitionTo } from 'browserHistory';
import * as ContentTypeActions from 'modules/content-types/details';
class ContentTypePage extends ObjectPage {
+ componentDidMount() {
+ this.props.actions.clearFetchErrors();
+ // this.props.actions.fetchSchema(this.props.namespace, true);
+ this.props.actions.fetchSchema('json',
+ [
+ {
+ "name": "content-type",
+ "kind": "contentType",
+ "schema": {
+ "type": "object",
+ "title": "Content Type",
+ "$schema": "http:\/\/json-schema.org\/draft-04\/schema#",
+ "properties": {
+ "discounts": {
+ "type": "array",
+ "items": {
+ "$ref": "#\/definitions\/discount"
+ }
+ },
+ "attributes": {
+ "type": "object",
+ "required": [
+ "title"
+ ],
+ "properties": {
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ],
+ },
+ "slug": {
+ "type": "string",
+ "minLength": 1
+ }
+ }
+ }
+ },
+ "definitions": {
+ "discount": {
+ "type": "object",
+ "title": "Discount",
+ "$schema": "http:\/\/json-schema.org\/draft-04\/schema#",
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "offer": {
+ "type": "object"
+ },
+ "title": {
+ "type": "string"
+ },
+ "qualifier": {
+ "type": "object"
+ },
+ "description": {
+ "type": "string",
+ "widget": "richText"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ );
+
+ if (this.isNew) {
+ this.props.actions.newEntity();
+ } else {
+ this.fetchEntity()
+ .then(({ payload }) => {
+ if (isArchived(payload)) this.transitionToList();
+ });
+ }
+
+ this.props.actions.fetchAmazonStatus()
+ .catch(() => {}); // pass
+ }
+
save(): ?Promise<*> {
let isNew = this.isNew;
let willBePromo = super.save();
diff --git a/ashes/src/components/content-types/form.js b/ashes/src/components/content-types/form.js
new file mode 100644
index 0000000000..fd31748806
--- /dev/null
+++ b/ashes/src/components/content-types/form.js
@@ -0,0 +1,70 @@
+/* @flow */
+
+// libs
+import React, { Component, Element } from 'react';
+import { autobind } from 'core-decorators';
+
+// components
+import ObjectFormInner from 'components/object-form/object-form-inner';
+import SaveCancel from 'components/core/save-cancel';
+
+type Props = {
+ title: string,
+ isVisible: boolean,
+ schema: object,
+ object: object,
+ fieldsToRender: Array
,
+ onCancel: Function,
+ onUpdateObject: Function,
+};
+
+type State = {
+ object: object,
+};
+
+export default class MyForm extends Component {
+ props: Props;
+
+ state: State = {
+ object: {},
+ };
+
+ @autobind
+ handleChange(object: object) {
+ this.setState({ object });
+ }
+
+ @autobind
+ handleSave() {
+ this.props.onSave(this.state.object);
+ this.props.onCancel();
+ this.setState({ object: {} });
+ }
+
+ get footer() {
+ const saveDisabled = false;
+
+ return ;
+ }
+
+ render() {
+ const props = this.props;
+
+ const attributes = {
+ ...props.object,
+ ...this.state.object,
+ };
+
+ return (
+
+
+ {this.footer}
+
+ );
+ }
+}
diff --git a/ashes/src/components/content-types/layout.json b/ashes/src/components/content-types/layout.json
index cfe43deb50..4605ff4710 100644
--- a/ashes/src/components/content-types/layout.json
+++ b/ashes/src/components/content-types/layout.json
@@ -5,11 +5,13 @@
"title": "General",
"content": [
{
- "canAddProperty": true,
+ "canAddProperty": false,
"includeRest": true,
"type": "fields",
"value": [
- "name"
+ "title",
+ "description",
+ "slug"
],
"omit": [
"storefrontName",
@@ -25,46 +27,7 @@
]
},
{
- "type": "group",
- "title": "Discounts",
- "content": [
- {
- "type": "discounts"
- },
- {
- "type": "customers"
- }
- ]
- },
- {
- "type": "group",
- "title": "Usage Rules",
- "content": [
- {
- "type": "usage-rules"
- }
- ]
- },
- {
- "type": "group",
- "showIfNew": true,
- "title": "Apply Type",
- "content": [
- {
- "type": "apply-type"
- }
- ]
- }
- ],
- "aside": [
- {
- "type": "state"
- },
- {
- "type": "tags"
- },
- {
- "type": "watchers"
+ "type": "columns"
}
]
}
diff --git a/ashes/src/components/content-types/modal.js b/ashes/src/components/content-types/modal.js
new file mode 100644
index 0000000000..5b88ddf184
--- /dev/null
+++ b/ashes/src/components/content-types/modal.js
@@ -0,0 +1,70 @@
+/* @flow */
+
+// libs
+import React, { Component, Element } from 'react';
+import { autobind } from 'core-decorators';
+
+// components
+import Modal from 'components/core/modal';
+import ObjectFormInner from 'components/object-form/object-form-inner';
+import SaveCancel from 'components/core/save-cancel';
+
+type Props = {
+ title: string,
+ isVisible: boolean,
+ schema: object,
+ object: object,
+ fieldsToRender: Array,
+ onCancel: Function,
+ onUpdateObject: Function,
+};
+
+type State = {
+ object: object,
+};
+
+export default class MyModal extends Component {
+ props: Props;
+
+ state: State = {
+ object: {},
+ };
+
+ @autobind
+ handleChange(object: object) {
+ this.setState({ object });
+ }
+
+ @autobind
+ handleSave() {
+ this.props.onSave(this.state.object);
+ this.props.onCancel();
+ this.setState({ object: {} });
+ }
+
+ get footer() {
+ const saveDisabled = false;
+
+ return ;
+ }
+
+ render() {
+ const props = this.props;
+
+ const attributes = {
+ ...props.object,
+ ...this.state.object,
+ };
+
+ return (
+
+
+
+ );
+ }
+}
diff --git a/ashes/src/components/object-page/object-details.css b/ashes/src/components/object-page/object-details.css
index cde4149f47..e813f42ca9 100644
--- a/ashes/src/components/object-page/object-details.css
+++ b/ashes/src/components/object-page/object-details.css
@@ -6,6 +6,15 @@
flex-wrap: wrap;
}
+.full-page {
+ width: 100%;
+ min-width: 400px;
+
+ & > div {
+ margin-bottom: 20px;
+ }
+}
+
.main {
width: calc(67% - 1.85%);
margin-right: 1.85%;
@@ -39,3 +48,45 @@
.customer-groups {
margin-top: 20px;
}
+
+.columns {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+}
+
+.column {
+ flex-grow: 1;
+ margin-top: 20px;
+ margin-right: 0;
+ border-left: 0;
+
+ &:first-child {
+ border-left: 1px solid #d9d9d9;
+ }
+}
+
+.column-body {
+ min-height: 600px;
+}
+
+.column-body > * {
+ width: 100%;
+}
+
+.column-body-empty {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+}
+
+.column-footer {
+ padding: 10px;
+ border-top: 1px solid #d9d9d9;
+}
+
+.column-footer > button {
+ width: 100%;
+}
diff --git a/ashes/src/components/object-page/object-details.jsx b/ashes/src/components/object-page/object-details.jsx
index a459e5c2e3..0edf3dcd8c 100644
--- a/ashes/src/components/object-page/object-details.jsx
+++ b/ashes/src/components/object-page/object-details.jsx
@@ -186,6 +186,16 @@ export default class ObjectDetails extends Component {
return addKeys(name, section.map(desc => this.renderNode(desc, section)));
}
+ get main() {
+ if (this.layout.main != null) {
+ return (
+
+ {this.renderSection('main')}
+
+ );
+ }
+ }
+
get aside() {
if (this.layout.aside != null) {
return (
@@ -202,7 +212,7 @@ export default class ObjectDetails extends Component {
return (
diff --git a/ashes/src/modules/object-schema.js b/ashes/src/modules/object-schema.js
index b94bab28f3..7e6b948a5d 100644
--- a/ashes/src/modules/object-schema.js
+++ b/ashes/src/modules/object-schema.js
@@ -6,7 +6,12 @@ import Api from 'lib/api';
const _fetchSchema = createAsyncActions(
'fetchSchema',
- (kind, id = void 0) => Api.get(`/object/schemas/byKind/${kind}`)
+ (kind, id = void 0) => {
+ if (kind === 'json') {
+ return Promise.resolve(id);
+ }
+ return Api.get(`/object/schemas/byKind/${kind}`);
+ }
);
export const saveSchema = createAction('SCHEMA_SAVE', (kind, schema) => [kind, schema]);
diff --git a/ashes/src/paragons/content-type.js b/ashes/src/paragons/content-type.js
index 8e4d585b8d..e8f47158e8 100644
--- a/ashes/src/paragons/content-type.js
+++ b/ashes/src/paragons/content-type.js
@@ -1,50 +1,103 @@
import { assoc } from 'sprout-data';
-function addEmptyDiscount(contentType) {
- const discount = {
- id: null,
+let id = 0;
+function uniqId() {
+ return id++;
+}
+
+export function addContentTypeObject(contentType, key, attributes) {
+ const object = {
+ id: uniqId(),
createdAt: null,
- attributes: {
- qualifier: {
- t: 'qualifier',
- v: {
- orderAny: {}
- }
+ attributes,
+ };
+
+ return {
+ ...contentType,
+ [key]: {
+ ...contentType[key],
+ byId: {
+ ...contentType[key].byId,
+ [object.id]: object,
},
- offer: {
- t: 'offer',
- v: {
- orderPercentOff: {}
- }
- }
+ allIds: [
+ ...contentType[key].allIds,
+ object.id,
+ ],
},
};
+}
- contentType.discounts.push(discount);
- return contentType;
+export function updateContentTypeObject(contentType, key, id, attributes) {
+ const object = {
+ id: id,
+ createdAt: null,
+ attributes,
+ };
+
+ return {
+ ...contentType,
+ [key]: {
+ ...contentType[key],
+ byId: {
+ ...contentType[key].byId,
+ [object.id]: object,
+ },
+ allIds: [
+ ...contentType[key].allIds,
+ object.id,
+ ],
+ },
+ };
+}
+
+export function removeContentTypeObject(contentType, key, id) {
+ const byId = contentType[key].byId;
+ delete byId[id];
+ return {
+ ...contentType,
+ [key]: {
+ ...contentType[key],
+ byId,
+ allIds: contentType[key].allIds.filter(itemId => itemId !== id),
+ },
+ };
+}
+
+function addEmptyTab(contentType) {
+ return addContentTypeObject(contentType, 'tabs', {
+ title: {
+ t: 'string',
+ v: 'Details',
+ },
+ });
}
export function createEmptyContentType() {
const contentType = {
id: null,
- applyType: 'auto',
- isExclusive: true,
createdAt: null,
attributes: {
- storefrontName: {
- t: 'richText',
- v: 'Storefront name'
- },
- customerGroupIds: {
- t: 'tock673sjgmqbi5zlfx43o4px6jnxi7absotzjvxwir7jo2v',
- v: null,
- },
+ title: null,
+ description: null,
+ slug: null,
+ },
+ tabs: {
+ byId: {},
+ allIds: [],
+ },
+ sections: {
+ byId: {},
+ allIds: [],
+ },
+ properties: {
+ byId: {},
+ allIds: [],
},
- discounts: [],
};
- return addEmptyDiscount(contentType);
+ return addEmptyTab(contentType);
}
export function setDiscountAttr(contentType, label, value) {