diff --git a/api/api.js b/api/api.js index e636d98..06dd41f 100644 --- a/api/api.js +++ b/api/api.js @@ -1,5 +1,3 @@ -require('../server.babel'); // babel registration (runtime transpilation for node) - import express from 'express'; import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; @@ -53,7 +51,7 @@ app.route('/users/:id/cart/:cartId') app.route('/users/:id/orders') .get(users.auth, users.isOwner, users.getUserOrders) - .post(users.auth, users.isOwner, users.validateCart); + .post(users.auth, users.isOwner, users.validateCart, users.load); app.route('/users/:id/orders/:orderId') .get(users.auth, users.isOwner, users.getUserOrder) diff --git a/api/functions/users.js b/api/functions/users.js index 4eec21b..2415419 100644 --- a/api/functions/users.js +++ b/api/functions/users.js @@ -322,23 +322,26 @@ function getUserCartItem(req, res) { } function updateCartItem(req, res) { - Cart.get(req.params.cartId).getJoin().run().then((cartItem) => { - req.user.cartTotal -= cartItem.product.price * cartItem.nbItem; - cartItem.nbItem = req.body.nbItem; - cartItem.save().then(() => { - req.user.cartTotal += cartItem.product.price * cartItem.nbItem; - req.user.save().then(() => { - res.json(req.user.getPublic()); - }, (error) => { - console.error(error.message); - res.status(400).json({msg: 'Something went wrong', err: error.message}); - }); - }, (error) => { - }); - }, (error) => { - console.error(error.message); - res.status(404).json({msg: 'Cart Item not found', err: error.message}); - }); + for (const cartPos in req.user.cart) { + if (req.user.cart[cartPos].id === req.params.cartId) { + req.user.cartTotal -= req.user.cart[cartPos].product.price * req.user.cart[cartPos].nbItem; + if (req.body.nbItem <= 0) { + res.status(400).json({msg: 'nbItem has to be a positive number'}); + } else { + req.user.cart[cartPos].nbItem = req.body.nbItem; + req.user.cartTotal += req.user.cart[cartPos].product.price * req.user.cart[cartPos].nbItem; + req.user.saveAll().then(() => { + res.json(req.user.getPublic()); + }, (error) => { + console.error(error.message); + res.status(400).json({msg: 'Something went wrong', err: error.message}); + }); + } + return; + } + } + console.error(error.message); + res.status(404).json({msg: 'Cart Item not found', err: error.message}); } function deleteCartItem(req, res) { @@ -370,7 +373,7 @@ function getUserOrders(req, res) { res.json(req.user.orders); } -function validateCart(req, res) { +function validateCart(req, res, next) { (new Order({ cartTotal: req.user.cartTotal, userId: req.user.id @@ -386,7 +389,7 @@ function validateCart(req, res) { deleteCart(req.user.cart).then(() => { req.user.cartTotal = 0; req.user.save().then(() => { - res.json(result); + next(); }, (error) => { console.error(error.message); res.status(400).json({msg: 'Something went wrong', err: error.message}); diff --git a/package.json b/package.json index 5f9494d..dbf7b08 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "Quentin 'Kant' Jaccarino", "name": "whatashop", - "version": "0.1.1", + "version": "0.1.2", "description": "Online Shopping Application", "homepage": "https://github.com/Justkant/WhatAShop#readme", "license": "MIT", @@ -102,7 +102,7 @@ "cookie-parser": "1.4.0", "express": "4.13.3", "file-loader": "0.8.5", - "history": "1.13.1", + "history": "1.15.0", "hoist-non-react-statics": "1.0.3", "http-proxy": "1.12.0", "jsonwebtoken": "5.4.1", @@ -119,16 +119,16 @@ "react-document-meta": "2.0.0", "react-dom": "0.14.3", "react-redux": "4.0.0", - "react-router": "1.0.0", + "react-router": "1.0.1", "redux": "3.0.4", - "redux-router": "1.0.0-beta4", + "redux-router": "1.0.0-beta5", "scroll-behavior": "0.3.0", "serialize-javascript": "1.1.2", "serve-favicon": "2.3.0", - "superagent": "1.4.0", - "thinky": "2.2.0", + "superagent": "1.5.0", + "thinky": "2.2.2", "url-loader": "0.5.7", - "webpack-isomorphic-tools": "2.2.18" + "webpack-isomorphic-tools": "2.2.21" }, "devDependencies": { "apidoc": "0.13.1", @@ -140,18 +140,18 @@ "babel-runtime": "5.8.29", "better-npm-run": "0.0.4", "chai": "3.4.1", - "clean-webpack-plugin": "0.1.4", + "clean-webpack-plugin": "0.1.5", "concurrently": "1.0.0", "css-loader": "0.23.0", - "eslint": "1.10.1", + "eslint": "1.10.3", "eslint-config-airbnb": "0.1.0", "eslint-loader": "1.1.1", - "eslint-plugin-import": "0.10.1", - "eslint-plugin-react": "3.10.0", + "eslint-plugin-import": "0.11.0", + "eslint-plugin-react": "3.11.3", "extract-text-webpack-plugin": "0.9.1", "json-loader": "0.5.4", "karma": "0.13.15", - "karma-chrome-launcher": "0.2.1", + "karma-chrome-launcher": "0.2.2", "karma-cli": "0.1.1", "karma-firefox-launcher": "0.1.7", "karma-mocha": "0.2.1", @@ -170,7 +170,7 @@ "stylus-loader": "1.4.2", "webpack": "1.12.9", "webpack-dev-middleware": "1.4.0", - "webpack-hot-middleware": "2.5.0" + "webpack-hot-middleware": "2.6.0" }, "engines": { "node": "4.x" diff --git a/src/client.js b/src/client.js index 7d8441d..23d0055 100644 --- a/src/client.js +++ b/src/client.js @@ -8,8 +8,8 @@ import createHistory from 'history/lib/createBrowserHistory'; import useScroll from 'scroll-behavior/lib/useStandardScroll'; import createStore from './redux/create'; import ApiClient from './helpers/ApiClient'; -import {Provider} from 'react-redux'; -import {reduxReactRouter, ReduxRouter} from 'redux-router'; +import { Provider } from 'react-redux'; +import { reduxReactRouter, ReduxRouter } from 'redux-router'; import getRoutes from './routes'; import makeRouteHooksSafe from './helpers/makeRouteHooksSafe'; @@ -18,10 +18,10 @@ const client = new ApiClient(); // Three different types of scroll behavior available. // Documented here: https://github.com/rackt/scroll-behavior -const scrollablehistory = useScroll(createHistory); +const scrollableHistory = useScroll(createHistory); const dest = document.getElementById('content'); -const store = createStore(reduxReactRouter, makeRouteHooksSafe(getRoutes), scrollablehistory, client, window.__data); +const store = createStore(reduxReactRouter, makeRouteHooksSafe(getRoutes), scrollableHistory, client, window.__data); const component = ( diff --git a/src/components/Navbar/Navbar.js b/src/components/Navbar/Navbar.js index 6d3d0a0..7cc91d2 100644 --- a/src/components/Navbar/Navbar.js +++ b/src/components/Navbar/Navbar.js @@ -47,7 +47,7 @@ export default class Navbar extends React.Component {
{user.cart.map(({product}) => { return ( - +

{product.title}

); diff --git a/src/components/Title/Title.js b/src/components/Title/Title.js index a4af674..171a375 100644 --- a/src/components/Title/Title.js +++ b/src/components/Title/Title.js @@ -2,11 +2,14 @@ import React, { Component, PropTypes } from 'react'; export default class Title extends Component { static propTypes = { - title: PropTypes.string + title: PropTypes.string, + showButton: PropTypes.bool, + button: PropTypes.string, + func: PropTypes.func }; render() { - const {title} = this.props; + const {title, showButton, button, func} = this.props; const styles = require('./Title.styl'); return ( @@ -14,6 +17,7 @@ export default class Title extends Component {
{title}
+ {showButton && }
); } diff --git a/src/components/Title/Title.styl b/src/components/Title/Title.styl index 0d4ef65..c77615e 100644 --- a/src/components/Title/Title.styl +++ b/src/components/Title/Title.styl @@ -11,8 +11,25 @@ align-items: center; margin-right: 30px; color: #999; + flex-grow: 1; + justify-content: center; span { font-weight: 300; } } + +button { + color: #999; + background: transparent; + border: 0; + cursor: pointer; + + &:hover { + color: #44c63d; + } + + &:focus { + outline: 0; + } +} diff --git a/src/components/__tests__/Search-test.js b/src/components/__tests__/Search-test.js index 0a80b32..ac285fb 100644 --- a/src/components/__tests__/Search-test.js +++ b/src/components/__tests__/Search-test.js @@ -1,10 +1,10 @@ /* eslint no-unused-expressions: 0*/ import React from 'react'; -import {renderIntoDocument} from 'react-addons-test-utils'; -import { expect} from 'chai'; +import { renderIntoDocument } from 'react-addons-test-utils'; +import { expect } from 'chai'; import { Search } from 'components'; import { Provider } from 'react-redux'; -import {reduxReactRouter} from 'redux-router'; +import { reduxReactRouter } from 'redux-router'; import createHistory from 'history/lib/createMemoryHistory'; import createStore from 'redux/create'; import ApiClient from 'helpers/ApiClient'; @@ -32,5 +32,4 @@ describe('Search', () => { it('should render correctly', () => { expect(renderer).to.be.ok; }); - }); diff --git a/src/containers/Cart/Cart.js b/src/containers/Cart/Cart.js index 3d4bbbe..20ff914 100644 --- a/src/containers/Cart/Cart.js +++ b/src/containers/Cart/Cart.js @@ -1,28 +1,55 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { Title } from 'components'; -import { deleteCartItem } from 'redux/modules/auth'; +import { deleteCartItem, updateCartItem, validateCart } from 'redux/modules/auth'; +import { pushState } from 'redux-router'; -@connect(state => ({user: state.auth.user}), { deleteCartItem }) +@connect(state => ({user: state.auth.user}), { deleteCartItem, updateCartItem, validateCart, pushState }) export default class Cart extends Component { static propTypes = { user: PropTypes.object, - deleteCartItem: PropTypes.func + deleteCartItem: PropTypes.func, + updateCartItem: PropTypes.func, + validateCart: PropTypes.func, + pushState: PropTypes.func }; + handleFocus(ev) { + ev.target.select(); + } + + inputHandler(productId, currentNbItem) { + const nb = parseInt(this.refs.inputNbItem.value, 10); + + if (nb && nb !== currentNbItem) { + this.updateItem.bind(this, productId, nb)(); + } + } + + updateItem(productId, nbItem) { + if (nbItem > 0) { + this.props.updateCartItem(this.props.user.id, productId, nbItem); + } + } + deleteItem(productId) { this.props.deleteCartItem(this.props.user.id, productId); } + validateCart() { + this.props.validateCart(this.props.user.id); + this.props.pushState(null, '/profile/orders'); + } + render() { const styles = require('./Cart.styl'); const {user} = this.props; return (
- + <Title title="Cart" showButton button="done" func={this.validateCart.bind(this)}/> <div className={styles.productContainer}> - {user && user.cart.map(({id, product, nbItem}) => { + {user && user.cart && user.cart.map(({id, product, nbItem}) => { return ( <div className={styles.element} key={product.id}> <div className={styles.imageContainer}> @@ -33,9 +60,9 @@ export default class Cart extends Component { <p>{product.price} $</p> <div className={styles.buttonContainer}> <div className={styles.inputNumber}> - <button><i className="material-icons">remove</i></button> - <input className={styles.number} defaultValue={nbItem}/> - <button><i className="material-icons">add</i></button> + <button onClick={this.updateItem.bind(this, id, nbItem - 1)}><i className="material-icons">remove</i></button> + <input className={styles.number} ref="inputNbItem" value={nbItem} onChange={this.inputHandler.bind(this, id, nbItem)} onFocus={this.handleFocus}/> + <button onClick={this.updateItem.bind(this, id, nbItem + 1)}><i className="material-icons">add</i></button> </div> <button className={styles.deleteButton} onClick={this.deleteItem.bind(this, id)}> <i className="material-icons">delete</i> diff --git a/src/containers/Cart/Cart.styl b/src/containers/Cart/Cart.styl index 501706c..65ade45 100644 --- a/src/containers/Cart/Cart.styl +++ b/src/containers/Cart/Cart.styl @@ -7,7 +7,7 @@ .productContainer { background-color: #f5f5f5; - padding: 20px; + padding: 20px 20px 0 20px; display: flex; flex-direction: column; overflow: auto; diff --git a/src/containers/Orders/Orders.js b/src/containers/Orders/Orders.js index 30b1d55..9d643fd 100644 --- a/src/containers/Orders/Orders.js +++ b/src/containers/Orders/Orders.js @@ -1,17 +1,20 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +@connect(state => ({user: state.auth.user})) export default class Orders extends Component { + static propTypes = { + user: PropTypes.object + }; + render() { + const { user } = this.props; const styles = require('./Orders.styl'); - const array = []; - for (let index = 0; index < 50; index++) { - array.push('Orders of kant'); - } return ( <div className={styles.container}> - {array.map((value, index) => { - return (<div className={styles.element} key={value + index}><h4>{value}</h4></div>); + {user && user.orders && user.orders.map((value, index) => { + return (<div className={styles.element} key={value.id + index}><h4>{value.cartTotal}</h4></div>); })} </div> ); diff --git a/src/helpers/__tests__/getStatusFromRoutes-test.js b/src/helpers/__tests__/getStatusFromRoutes-test.js index 61abd43..db41083 100644 --- a/src/helpers/__tests__/getStatusFromRoutes-test.js +++ b/src/helpers/__tests__/getStatusFromRoutes-test.js @@ -2,7 +2,6 @@ import { expect } from 'chai'; import getStatusFromRoutes from '../getStatusFromRoutes'; describe('getStatusFromRoutes', () => { - it('should return null when no routes have status code', () => { const status = getStatusFromRoutes([ {}, {} diff --git a/src/helpers/__tests__/makeRouteHooksSafe-test.js b/src/helpers/__tests__/makeRouteHooksSafe-test.js index cba3b38..9c49dd7 100644 --- a/src/helpers/__tests__/makeRouteHooksSafe-test.js +++ b/src/helpers/__tests__/makeRouteHooksSafe-test.js @@ -5,7 +5,6 @@ import makeRouteHooksSafe from '../makeRouteHooksSafe'; describe('makeRouteHooksSafe', () => { - it('should work with JSX routes', () => { const onEnter = () => { throw new Error('Shouldn\'t call onEnter'); diff --git a/src/helpers/connectData.js b/src/helpers/connectData.js index 4ad6727..8865c0a 100644 --- a/src/helpers/connectData.js +++ b/src/helpers/connectData.js @@ -8,7 +8,6 @@ import hoistStatics from 'hoist-non-react-statics'; */ export default function connectData(fetchData, fetchDataDeferred) { - return function wrapWithFetchData(WrappedComponent) { class ConnectData extends Component { render() { diff --git a/src/redux/middleware/transitionMiddleware.js b/src/redux/middleware/transitionMiddleware.js index f2fd94f..0a2428b 100644 --- a/src/redux/middleware/transitionMiddleware.js +++ b/src/redux/middleware/transitionMiddleware.js @@ -11,7 +11,6 @@ export default ({getState, dispatch}) => next => action => { const {components, location, params} = action.payload; const promise = new Promise((resolve) => { - const doTransition = () => { next(action); Promise.all(getDataDependencies(components, getState, dispatch, location, params, true)) diff --git a/src/redux/modules/auth.js b/src/redux/modules/auth.js index 2f708ac..ca3df54 100644 --- a/src/redux/modules/auth.js +++ b/src/redux/modules/auth.js @@ -25,6 +25,12 @@ const ADDCART_FAIL = 'auth/ADDCART_FAIL'; const DELCART = 'auth/DELCART'; const DELCART_SUCCESS = 'auth/DELCART_SUCCESS'; const DELCART_FAIL = 'auth/DELCART_FAIL'; +const UPDATECART = 'auth/UPDATECART'; +const UPDATECART_SUCCESS = 'auth/UPDATECART_SUCCESS'; +const UPDATECART_FAIL = 'auth/UPDATECART_FAIL'; +const VALIDATECART = 'auth/VALIDATECART'; +const VALIDATECART_SUCCESS = 'auth/VALIDATECART_SUCCESS'; +const VALIDATECART_FAIL = 'auth/VALIDATECART_FAIL'; const initialState = { loaded: false @@ -191,6 +197,40 @@ export default function reducer(state = initialState, action = {}) { deletingCart: false, cartDelError: action.error }; + case UPDATECART: + return { + ...state, + updatingCart: true + }; + case UPDATECART_SUCCESS: + return { + ...state, + updatingCart: false, + user: action.result + }; + case UPDATECART_FAIL: + return { + ...state, + updatingCart: false, + cartUpdateError: action.error + }; + case VALIDATECART: + return { + ...state, + validatingCart: true + }; + case VALIDATECART_SUCCESS: + return { + ...state, + validatingCart: false, + user: action.result + }; + case VALIDATECART_FAIL: + return { + ...state, + validatingCart: false, + cartUpdateError: action.error + }; default: return state; } @@ -266,3 +306,17 @@ export function deleteCartItem(userId, cartId) { promise: (client) => client.del('/users/' + userId + '/cart/' + cartId) }; } + +export function updateCartItem(userId, cartId, nbItem) { + return { + types: [UPDATECART, UPDATECART_SUCCESS, UPDATECART_FAIL], + promise: (client) => client.put('/users/' + userId + '/cart/' + cartId, {data: {nbItem: nbItem}}) + }; +} + +export function validateCart(userId) { + return { + types: [VALIDATECART, VALIDATECART_SUCCESS, VALIDATECART_FAIL], + promise: (client) => client.post('/users/' + userId + '/orders') + }; +} diff --git a/src/routes.js b/src/routes.js index 3e69251..6cc745a 100644 --- a/src/routes.js +++ b/src/routes.js @@ -21,7 +21,7 @@ import { export default function(store) { const requireAuth = (nextState, replaceState, cb) => { function checkAuth() { - const { auth: { user }} = store.getState(); + const { auth: { user } } = store.getState(); if (!user) { replaceState(null, '/signup'); } @@ -37,7 +37,7 @@ export default function(store) { const alreadyAuth = (nextState, replaceState, cb) => { function checkAuth() { - const { auth: { user }} = store.getState(); + const { auth: { user } } = store.getState(); if (user) { replaceState(null, '/'); } @@ -53,7 +53,7 @@ export default function(store) { const requireAdmin = (nextState, replaceState, cb) => { function checkAdmin() { - const { auth: { user }} = store.getState(); + const { auth: { user } } = store.getState(); if (!user || !user.admin) { replaceState(null, '/'); } diff --git a/src/server.js b/src/server.js index 53b100c..da133f0 100644 --- a/src/server.js +++ b/src/server.js @@ -11,10 +11,10 @@ import ApiClient from './helpers/ApiClient'; import Html from './helpers/Html'; import PrettyError from 'pretty-error'; -import {ReduxRouter} from 'redux-router'; +import { ReduxRouter } from 'redux-router'; import createHistory from 'history/lib/createMemoryHistory'; -import {reduxReactRouter, match} from 'redux-router/server'; -import {Provider} from 'react-redux'; +import { reduxReactRouter, match } from 'redux-router/server'; +import { Provider } from 'react-redux'; import qs from 'query-string'; import getRoutes from './routes'; import getStatusFromRoutes from './helpers/getStatusFromRoutes'; @@ -46,9 +46,9 @@ proxy.on('error', (error, req, res) => { console.error('proxy error', error); } if (!res.headersSent) { - res.writeHead(500, {'content-type': 'application/json'}); + res.writeHead(500, { 'content-type': 'application/json' }); } - res.end(JSON.stringify({error: 'proxy_error', reason: error.message})); + res.end(JSON.stringify({ error: 'proxy_error', reason: error.message })); }); app.use((req, res) => {