diff --git a/.babelrc b/.babelrc
index 1e0e1722..2b94f11e 100644
--- a/.babelrc
+++ b/.babelrc
@@ -21,7 +21,12 @@
"transform-es2015-sticky-regex",
"transform-es2015-template-literals",
"transform-es2015-typeof-symbol",
- "transform-es2015-unicode-regex"
+ "transform-es2015-unicode-regex",
+ ["typecheck", {
+ "disable": {
+ "production": true
+ }
+ }]
],
"presets": ["stage-0", "react"],
"env": {
diff --git a/.eslintrc b/.eslintrc
index 05de1f8d..cad7691b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -23,7 +23,8 @@
"ignorePattern": "^\\s*var\\s.+=\\s*require\\s*\\("
}
],
- "arrow-body-style": 0
+ "arrow-body-style": 0,
+ "react/prop-types": 0
},
"parser": "babel-eslint",
}
diff --git a/package.json b/package.json
index 3e38d647..f49d5712 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"babel-plugin-transform-class-properties": "^6.5.2",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-runtime": "^6.5.0",
+ "babel-plugin-typecheck": "^3.7.0",
"babel-polyfill": "^6.5.0",
"babel-preset-es2015": "^6.5.0",
"babel-preset-react": "^6.5.0",
diff --git a/src/components/categories/categories.jsx b/src/components/categories/categories.jsx
index 5ea63a34..82ad17b2 100644
--- a/src/components/categories/categories.jsx
+++ b/src/components/categories/categories.jsx
@@ -5,6 +5,7 @@ import _ from 'lodash';
import styles from './categories.css';
import cssModules from 'react-css-modules';
import { connect } from 'react-redux';
+import type { HTMLElement } from '../../types';
import * as actions from '../../modules/categories';
@@ -21,7 +22,7 @@ class Categories extends React.Component {
this.props.fetchCategories();
}
- render(): Element {
+ render(): HTMLElement {
const categoryItems = _.map(this.props.list, (item) => {
const key = `category-${item.replace(/\s/g, '-')}`;
return (
diff --git a/src/components/common/icon.jsx b/src/components/common/icon.jsx
index 3836e0a7..6dba6f80 100644
--- a/src/components/common/icon.jsx
+++ b/src/components/common/icon.jsx
@@ -1,9 +1,10 @@
/* @flow */
import classNames from 'classnames';
-import React, { PropTypes, Element } from 'react';
+import React from 'react';
+import type { HTMLElement } from '../../types';
-function wrapSpinner(svg: Element, className: string) {
+function wrapSpinner(svg: HTMLElement, className: string) {
if (className.indexOf('spinner') > -1) {
return (
{svg}
@@ -13,7 +14,15 @@ function wrapSpinner(svg: Element, className: string) {
return svg;
}
-const Icon = (props: Object) => {
+type IconSize = 'm' | 'l' | 'xl' | 'xxl';
+
+type IconProps = {
+ size: ?IconSize;
+ name: string,
+ className: ?string,
+}
+
+const Icon = (props: IconProps) => {
const name = `#${props.name}-icon`;
const useTag = ``;
@@ -36,10 +45,4 @@ const Icon = (props: Object) => {
);
};
-Icon.propTypes = {
- size: PropTypes.oneOf(['m', 'l', 'xl', 'xxl']),
- name: PropTypes.string.isRequired,
- className: PropTypes.string,
-};
-
export default Icon;
diff --git a/src/components/editable-block/index.css b/src/components/editable-block/index.css
new file mode 100644
index 00000000..aa328da8
--- /dev/null
+++ b/src/components/editable-block/index.css
@@ -0,0 +1,24 @@
+@import "colors.css";
+
+.editable-block {
+ padding: 20px;
+ background: var(--color-panels-bg);
+}
+
+.header {
+ margin-top: 5px;
+ display: flex;
+}
+
+.title {
+ font-size: 22px;
+ line-height: 27px;
+ flex-grow: 1;
+}
+
+.edit {
+ color: #9E9E9E;
+ text-align: right;
+}
+
+.content {}
diff --git a/src/components/editable-block/index.jsx b/src/components/editable-block/index.jsx
new file mode 100644
index 00000000..d17e4602
--- /dev/null
+++ b/src/components/editable-block/index.jsx
@@ -0,0 +1,37 @@
+
+/* @flow */
+
+import React from 'react';
+import cssModules from 'react-css-modules';
+import styles from './index.css';
+
+import type { HTMLElement } from '../../types';
+
+type EditableProps = {
+ isEditing: boolean;
+ viewContent: ?HTMLElement|string;
+ editContent: ?HTMLElement|string;
+ title: string;
+};
+
+const EditableBlock = (props: EditableProps) => {
+ const content = props.isEditing ? props.editContent : props.viewContent;
+
+ const editLink = props.isEditing ? EDIT
: null;
+
+ return (
+
+
+
{props.title}
+ {editLink}
+
+
{content}
+
+ );
+};
+
+EditableBlock.defaulProps = {
+ isEditing: false,
+};
+
+export default cssModules(EditableBlock, styles);
diff --git a/src/components/forms/formfield.css b/src/components/forms/formfield.css
index ce5d11c1..b58b50aa 100644
--- a/src/components/forms/formfield.css
+++ b/src/components/forms/formfield.css
@@ -4,11 +4,13 @@
.has-error {
& > input,
& > textarea {
- border-color: var(--redish);
+ border-color: var(--color-error);
}
}
.error {
color: var(--redish);
text-align: left;
+ font-size: 13px;
+ line-height: 16px;
}
diff --git a/src/components/pages/auth/auth.css b/src/components/pages/auth/auth.css
index bfaf3b75..b0c61d52 100644
--- a/src/components/pages/auth/auth.css
+++ b/src/components/pages/auth/auth.css
@@ -10,7 +10,7 @@
lost-offset: -4/11;
}
-.icon {
+.logo {
width: 96px;
height: 96px;
margin-bottom: 48px;
@@ -86,7 +86,7 @@
margin-bottom: 20px;
}
- .icon {
+ .logo {
width: 48px;
height: 48px;
margin-bottom: 20px;
diff --git a/src/components/pages/auth/auth.jsx b/src/components/pages/auth/auth.jsx
index 7ab9a09c..47b657c0 100644
--- a/src/components/pages/auth/auth.jsx
+++ b/src/components/pages/auth/auth.jsx
@@ -9,7 +9,7 @@ import Icon from '../../common/icon';
const Auth = props => {
return (
-
+
{props.children}
);
diff --git a/src/components/pages/auth/login.jsx b/src/components/pages/auth/login.jsx
index 4a00047d..cf02bd38 100644
--- a/src/components/pages/auth/login.jsx
+++ b/src/components/pages/auth/login.jsx
@@ -1,6 +1,6 @@
/* @flow */
-import React, { Component, Element } from 'react';
+import React, { Component } from 'react';
import cssModules from 'react-css-modules';
import styles from './auth.css';
import { autobind } from 'core-decorators';
@@ -11,6 +11,8 @@ import Button from '../../common/buttons';
import WrapToLines from '../../common/wrap-to-lines';
import { Link } from 'react-router';
+import type { HTMLElement } from '../../../types';
+
type AuthState = {
email: string,
@@ -41,7 +43,7 @@ export default class Auth extends Component {
});
}
- render(): Element {
+ render(): HTMLElement {
const { password, email } = this.state;
return (
diff --git a/src/components/pages/auth/reset-password.jsx b/src/components/pages/auth/reset-password.jsx
index b9c7b811..396d93ec 100644
--- a/src/components/pages/auth/reset-password.jsx
+++ b/src/components/pages/auth/reset-password.jsx
@@ -1,6 +1,6 @@
/* @flow */
-import React, { Component, Element, PropTypes } from 'react';
+import React, { Component, PropTypes } from 'react';
import cssModules from 'react-css-modules';
import styles from './auth.css';
import { autobind } from 'core-decorators';
@@ -12,6 +12,8 @@ import { TextInput } from '../../common/inputs';
import { FormField } from '../../forms';
import Button from '../../common/buttons';
+import type { HTMLElement } from '../../../types';
+
type ResetState = {
isReseted: boolean;
};
@@ -74,7 +76,7 @@ export default class RestorePassword extends Component {
return Promise.resolve({_error: null});
}
- get topMessage(): Element {
+ get topMessage(): HTMLElement {
const { isReseted } = this.state;
const { error } = this.props;
@@ -101,7 +103,7 @@ export default class RestorePassword extends Component {
);
}
- get passwordFields(): ?Array {
+ get passwordFields(): ?HTMLElement[] {
const { isReseted } = this.state;
const { fields: {passwd1, passwd2}} = this.props;
@@ -122,7 +124,7 @@ export default class RestorePassword extends Component {
this.props.dispatch(routeActions.push('/login'));
}
- get primaryButton(): Element {
+ get primaryButton(): HTMLElement {
const { isReseted } = this.state;
if (isReseted) {
@@ -134,7 +136,7 @@ export default class RestorePassword extends Component {
return ;
}
- render(): Element {
+ render(): HTMLElement {
const { handleSubmit } = this.props;
return (
diff --git a/src/components/pages/auth/restore-password.jsx b/src/components/pages/auth/restore-password.jsx
index d4bf4247..bda38424 100644
--- a/src/components/pages/auth/restore-password.jsx
+++ b/src/components/pages/auth/restore-password.jsx
@@ -1,6 +1,6 @@
/* @flow */
-import React, { Component, Element, PropTypes } from 'react';
+import React, { Component, PropTypes } from 'react';
import cssModules from 'react-css-modules';
import styles from './auth.css';
import { autobind } from 'core-decorators';
@@ -13,6 +13,8 @@ import { FormField } from '../../forms';
import Button from '../../common/buttons';
import { Link } from 'react-router';
+import type { HTMLElement } from '../../../types';
+
type FormData = {
email: string;
};
@@ -59,7 +61,7 @@ export default class RestorePassword extends Component {
});
}
- get topMessage(): Element {
+ get topMessage(): HTMLElement {
const { emailSent } = this.state;
const { fields: {email}, error } = this.props;
@@ -86,7 +88,7 @@ export default class RestorePassword extends Component {
);
}
- get emailField(): ?Element {
+ get emailField(): ?HTMLElement {
const { emailSent } = this.state;
const { fields: {email}} = this.props;
@@ -104,7 +106,7 @@ export default class RestorePassword extends Component {
this.props.dispatch(routeActions.push('/login'));
}
- get primaryButton(): Element {
+ get primaryButton(): HTMLElement {
const { emailSent } = this.state;
if (emailSent) {
@@ -116,7 +118,7 @@ export default class RestorePassword extends Component {
return ;
}
- get switchStage(): Element {
+ get switchStage(): ?HTMLElement {
const { emailSent } = this.state;
if (!emailSent) {
@@ -128,7 +130,7 @@ export default class RestorePassword extends Component {
}
}
- render(): Element {
+ render(): HTMLElement {
const {handleSubmit} = this.props;
return (
diff --git a/src/components/pages/auth/signup.jsx b/src/components/pages/auth/signup.jsx
index 18f6c276..65e58dbf 100644
--- a/src/components/pages/auth/signup.jsx
+++ b/src/components/pages/auth/signup.jsx
@@ -1,6 +1,6 @@
/* @flow */
-import React, { Component, Element } from 'react';
+import React, { Component } from 'react';
import cssModules from 'react-css-modules';
import styles from './auth.css';
import { autobind } from 'core-decorators';
@@ -11,6 +11,8 @@ import Button from '../../common/buttons';
import WrapToLines from '../../common/wrap-to-lines';
import { Link } from 'react-router';
+import type { HTMLElement } from '../../../types';
+
type AuthState = {
email: string,
password: string,
@@ -49,7 +51,7 @@ export default class Auth extends Component {
});
}
- render(): Element {
+ render(): HTMLElement {
const { email, password, username } = this.state;
return (
diff --git a/src/components/pages/checkout/checkout.css b/src/components/pages/checkout/checkout.css
new file mode 100644
index 00000000..55bbf12d
--- /dev/null
+++ b/src/components/pages/checkout/checkout.css
@@ -0,0 +1,28 @@
+
+@import "media-queries.css";
+
+.checkout {
+ text-align: center;
+ margin-top: 45px;
+
+ lost-column: 9/11;
+ lost-offset: -1/11;
+}
+
+.logo {
+ width: 45px;
+ height: 45px;
+ margin: 0 auto;
+ margin-bottom: 40px;
+}
+
+@media (--medium-viewport) {
+ .checkout {
+ margin-top: 10px;
+ }
+
+ .logo {
+ width: 40px;
+ height: 40px;
+ }
+}
diff --git a/src/components/pages/checkout/checkout.jsx b/src/components/pages/checkout/checkout.jsx
new file mode 100644
index 00000000..b06e2b4c
--- /dev/null
+++ b/src/components/pages/checkout/checkout.jsx
@@ -0,0 +1,23 @@
+/* @flow */
+
+import React, { PropTypes } from 'react';
+import cssModules from 'react-css-modules';
+import styles from './checkout.css';
+
+import Icon from '../../common/icon';
+import Shipping from './shipping';
+
+const Checkout = () => {
+ return (
+
+
+
+
+ );
+};
+
+Checkout.propTypes = {
+ children: PropTypes.node,
+};
+
+export default cssModules(Checkout, styles);
diff --git a/src/components/pages/checkout/shipping.jsx b/src/components/pages/checkout/shipping.jsx
new file mode 100644
index 00000000..ec169d1a
--- /dev/null
+++ b/src/components/pages/checkout/shipping.jsx
@@ -0,0 +1,24 @@
+
+/* @flow */
+
+import React from 'react';
+import EditableBlock from '../../editable-block';
+import cssModules from 'react-css-modules';
+import styles from './checkout.css';
+
+type ShippingProps = {
+ isEditing: ?boolean;
+};
+
+const Shipping = (props: ShippingProps) => {
+ return (
+
+ );
+};
+
+export default cssModules(Shipping, styles);
diff --git a/src/css/colors.css b/src/css/colors.css
index 7f570ad9..e7d3f188 100644
--- a/src/css/colors.css
+++ b/src/css/colors.css
@@ -10,4 +10,5 @@
:root {
--color-error: var(--redish);
+ --color-panels-bg: #F4F4F4;
}
diff --git a/src/modules/cart.js b/src/modules/cart.js
new file mode 100644
index 00000000..8df452cb
--- /dev/null
+++ b/src/modules/cart.js
@@ -0,0 +1,10 @@
+
+import {createReducer} from 'redux-act';
+
+const initialState = {
+ skus: [],
+};
+
+const reducer = createReducer(void 0, initialState);
+
+export default reducer;
diff --git a/src/modules/checkout.js b/src/modules/checkout.js
new file mode 100644
index 00000000..0d7b0680
--- /dev/null
+++ b/src/modules/checkout.js
@@ -0,0 +1,9 @@
+
+import {createReducer} from 'redux-act';
+
+const initialState = {
+};
+
+const reducer = createReducer(void 0, initialState);
+
+export default reducer;
diff --git a/src/modules/index.js b/src/modules/index.js
index f0d40b7e..afecd8e2 100644
--- a/src/modules/index.js
+++ b/src/modules/index.js
@@ -2,13 +2,16 @@
import { combineReducers } from 'redux';
import { routeReducer } from 'react-router-redux';
import {reducer as formReducer} from 'redux-form';
-
+import cart from './cart';
+import checkout from './checkout';
import categories from './categories';
const reducer = combineReducers({
routing: routeReducer,
form: formReducer,
categories,
+ cart,
+ checkout,
});
export default reducer;
diff --git a/src/routes.jsx b/src/routes.jsx
index 51dec8ec..64dc9588 100644
--- a/src/routes.jsx
+++ b/src/routes.jsx
@@ -8,6 +8,8 @@ import Login from './components/pages/auth/login';
import SignUp from './components/pages/auth/signup';
import RestorePassword from './components/pages/auth/restore-password';
import ResetPassword from './components/pages/auth/reset-password';
+
+import Checkout from './components/pages/checkout/checkout';
import Grid from './components/pages/grid';
const routes = (
@@ -21,6 +23,7 @@ const routes = (
+
);
diff --git a/src/types.js b/src/types.js
new file mode 100644
index 00000000..bd4c8dfc
--- /dev/null
+++ b/src/types.js
@@ -0,0 +1,6 @@
+
+import React from 'react';
+
+export function HTMLElement(element) {
+ return React.isValidElement(element);
+}
diff --git a/tasks/server.js b/tasks/server.js
index b42725e6..6023797b 100644
--- a/tasks/server.js
+++ b/tasks/server.js
@@ -2,6 +2,7 @@
/* eslint camelcase: 0 */
+const _ = require('lodash');
const child_process = require('child_process');
const runSequence = require('run-sequence');
@@ -14,14 +15,22 @@ function affectsServer(task) {
module.exports = function(gulp) {
let node = null;
+ function killServer(cb) {
+ if (node) {
+ node.once('close', cb);
+ node.kill();
+ node = null;
+ } else {
+ cb();
+ }
+ }
+
let affectTasksRunning = 0;
function checkForPause(e) {
if (e.task in affectsServerTasks) {
affectTasksRunning++;
- process.nextTick(function() {
- runSequence('server.stop');
- });
+ killServer(_.noop);
}
}
@@ -38,15 +47,7 @@ module.exports = function(gulp) {
}
}
- gulp.task('server.stop', function(cb) {
- if (node) {
- node.once('close', cb);
- node.kill();
- node = null;
- } else {
- cb();
- }
- });
+ gulp.task('server.stop', killServer);
gulp.task('server.invalidate', function(cb) {
if (node) {
@@ -84,9 +85,12 @@ module.exports = function(gulp) {
gulp.watch(['server/**.*.js', 'src/server.jsx'], ['server.restart']);
});
- process.on('exit', function() {
+ function silentlyKill() {
if (node) node.kill();
- });
+ }
+
+ process.on('exit', silentlyKill);
+ process.on('uncaughtException', silentlyKill);
};
module.exports.affectsServer = affectsServer;