diff --git a/package.json b/package.json
index 3c33187..fc61684 100644
--- a/package.json
+++ b/package.json
@@ -9,12 +9,17 @@
"bootstrap": "^4.0.0",
"classnames": "^2.2.5",
"express": "^4.16.2",
+ "lodash": "4.17.10",
"mongoose": "^4.13.6",
"react": "^16.0.0",
"react-dom": "^16.0.0",
+ "react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
"react-scripts": "1.0.17",
"reactstrap": "^5.0.0-beta.3",
+ "redux": "^4.0.0",
+ "redux-pack": "0.1.5",
+ "redux-thunk": "^2.2.0",
"uuid": "^3.1.0"
},
"devDependencies": {
diff --git a/src/client/components/AddToCart.js b/src/client/components/AddToCart.js
new file mode 100644
index 0000000..f26366c
--- /dev/null
+++ b/src/client/components/AddToCart.js
@@ -0,0 +1,34 @@
+import React from "react";
+import { bindActionCreators } from "redux";
+import { connect } from "react-redux";
+import { Button } from "reactstrap";
+import * as actionCreators from "../redux/actions";
+
+class AddToCart extends React.Component {
+ render() {
+ const {
+ actions: { addItemsToCart },
+ cartItems: { isLoading },
+ product
+ } = this.props;
+ return (
+
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ cartItems: state.cartItems
+});
+
+const mapDispatchToProps = dispatch => ({
+ actions: bindActionCreators(actionCreators, dispatch)
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(AddToCart);
diff --git a/src/client/components/App.js b/src/client/components/App.js
new file mode 100644
index 0000000..6fb283b
--- /dev/null
+++ b/src/client/components/App.js
@@ -0,0 +1,27 @@
+import React from "react";
+import { Route, Switch } from "react-router-dom";
+import Home from "./Home";
+import Products from "./Products";
+import Product from "./Product";
+import Cart from "./Cart";
+import Header from "./Header";
+import { Container } from "reactstrap";
+
+export default class App extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/client/components/Cart.js b/src/client/components/Cart.js
new file mode 100644
index 0000000..82fe003
--- /dev/null
+++ b/src/client/components/Cart.js
@@ -0,0 +1,39 @@
+import React from "react";
+import { ListGroup, ListGroupItem, Alert } from "reactstrap";
+import DeleteCartItem from "./DeleteCartItem";
+import { connect } from "react-redux";
+
+function CartItem({ cartItem }) {
+ return (
+
+
+
{cartItem.product.name}
+
+
+
+
+
+ );
+}
+
+export class Cart extends React.Component {
+ render() {
+ const { cartItems } = this.props;
+ if (!cartItems.ids.length) {
+ return Cart is empty;
+ }
+ return (
+
+ {Object.values(cartItems.byId).map(cartItem => (
+
+ ))}
+
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ cartItems: state.cartItems
+});
+
+export default connect(mapStateToProps)(Cart);
diff --git a/src/client/components/CartBadge.js b/src/client/components/CartBadge.js
new file mode 100644
index 0000000..3784d1e
--- /dev/null
+++ b/src/client/components/CartBadge.js
@@ -0,0 +1,32 @@
+import React from "react";
+import { bindActionCreators } from "redux";
+import { connect } from "react-redux";
+import * as actions from "../redux/actions";
+import { Badge } from "reactstrap";
+
+class CartBadge extends React.Component {
+ componentDidMount() {
+ const { actions } = this.props;
+ actions.getCartItems();
+ }
+
+ render() {
+ const { cartItems } = this.props;
+ if (!cartItems.ids.length) {
+ return null;
+ }
+ return cartItems.ids.length ? (
+ {cartItems.ids.length}
+ ) : null;
+ }
+}
+
+const mapStateToProps = state => ({
+ cartItems: state.cartItems
+});
+
+const mapDispatchToProps = dispatch => ({
+ actions: bindActionCreators(actions, dispatch)
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(CartBadge);
diff --git a/src/client/components/DeleteCartItem.js b/src/client/components/DeleteCartItem.js
new file mode 100644
index 0000000..eaf110a
--- /dev/null
+++ b/src/client/components/DeleteCartItem.js
@@ -0,0 +1,7 @@
+import React from "react";
+
+export default class DeleteCartItem extends React.Component {
+ render() {
+ return
;
+ }
+}
diff --git a/src/client/components/Header.js b/src/client/components/Header.js
new file mode 100644
index 0000000..bb1fc5b
--- /dev/null
+++ b/src/client/components/Header.js
@@ -0,0 +1,60 @@
+import React from "react";
+import { Navbar, NavbarBrand, Nav, NavItem } from "reactstrap";
+import { Link } from "react-router-dom";
+import classnames from "classnames";
+import CartBadge from "./CartBadge";
+
+export default class Header extends React.Component {
+ render() {
+ const { location: { pathname } } = this.props;
+ return (
+
+
+ Apollo Store
+
+
+
+ );
+ }
+}
diff --git a/src/client/components/Home.js b/src/client/components/Home.js
new file mode 100644
index 0000000..b4af15f
--- /dev/null
+++ b/src/client/components/Home.js
@@ -0,0 +1,7 @@
+import React from "react";
+
+export default class Home extends React.Component {
+ render() {
+ return Welcome to apollo store
;
+ }
+}
diff --git a/src/client/components/Price.js b/src/client/components/Price.js
new file mode 100644
index 0000000..51afe37
--- /dev/null
+++ b/src/client/components/Price.js
@@ -0,0 +1,5 @@
+import React from "react";
+
+export default function Price({ value }) {
+ return ₹ {value};
+}
diff --git a/src/client/components/Product.js b/src/client/components/Product.js
new file mode 100644
index 0000000..4166004
--- /dev/null
+++ b/src/client/components/Product.js
@@ -0,0 +1,51 @@
+import React from "react";
+import { bindActionCreators } from "redux";
+import { connect } from "react-redux";
+import { Media } from "reactstrap";
+import * as actionCreators from "../redux/actions";
+import AddToCart from "./AddToCart";
+import Price from "./Price";
+
+class Product extends React.Component {
+ componentDidMount() {
+ const { product, actions, match } = this.props;
+ if (!product) {
+ const productId = parseInt(match.params.id, 10);
+ actions.getProducts(productId);
+ }
+ }
+
+ render() {
+ const { products, match } = this.props;
+ const productId = parseInt(match.params.id, 10);
+ const product = products.ids.length && products.byId[productId];
+ if (!product) {
+ return null;
+ }
+ return (
+
+
+
+
+
+ {product.name}
+ {product.description}
+
+
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state, props) => ({
+ products: state.products
+});
+
+const mapDisptachToProps = dispatch => ({
+ actions: bindActionCreators(actionCreators, dispatch)
+});
+
+export default connect(mapStateToProps, mapDisptachToProps)(Product);
diff --git a/src/client/components/ProductSelect.js b/src/client/components/ProductSelect.js
new file mode 100644
index 0000000..dbf9b72
--- /dev/null
+++ b/src/client/components/ProductSelect.js
@@ -0,0 +1,7 @@
+import React from "react";
+
+export default class ProductSelect extends React.Component {
+ render() {
+ return ;
+ }
+}
diff --git a/src/client/components/Products.js b/src/client/components/Products.js
new file mode 100644
index 0000000..de57f24
--- /dev/null
+++ b/src/client/components/Products.js
@@ -0,0 +1,71 @@
+import React from "react";
+import { bindActionCreators } from "redux";
+import { connect } from "react-redux";
+import { Link } from "react-router-dom";
+import { ListGroup, ListGroupItem } from "reactstrap";
+import * as productActionCreators from "../redux/actions";
+import Price from "./Price";
+import ProductSelect from "./ProductSelect";
+
+function Product({ product }) {
+ return (
+
+
+
+ );
+}
+
+class Products extends React.Component {
+ componentDidMount() {
+ const { productActions, match } = this.props;
+ const brand = match.params.brand;
+ productActions.getProducts(brand);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { match, productActions } = this.props;
+ if (nextProps.match.params.brand !== match.params.brand) {
+ productActions.getProducts(nextProps.match.params.brand);
+ }
+ }
+
+ render() {
+ const { products, match } = this.props;
+ // const brand = match.params.brand;
+ // const filteredProducts =
+ // brand ?
+ // Object.values(products.byId).filter(product => {
+ // console.log(product.brand);
+ // return product.brand.toLowerCase() === brand.toLowerCase();
+ // })
+ // : Object.values(products.byId);
+ return (
+
+ Products
+
+ {Object.values(products.byId).map(product => (
+
+ ))}
+
+
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ products: state.products
+});
+
+const mapDispatchToProps = dispatch => ({
+ productActions: bindActionCreators(productActionCreators, dispatch)
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Products);
diff --git a/src/client/redux/actionTypes/index.js b/src/client/redux/actionTypes/index.js
new file mode 100644
index 0000000..ce5bcf9
--- /dev/null
+++ b/src/client/redux/actionTypes/index.js
@@ -0,0 +1,12 @@
+export const GET_PRODUCTS = "GET_PRODUCTS";
+export const GET_CART_ITEMS = "GET_CART_ITEMS";
+export const ADD_ITEMS_TO_CART = "ADD_ITEMS_TO_CART";
+export const GET_PRODUCTS_REQUEST = "GET_PRODUCTS_REQUEST";
+export const GET_PRODUCTS_SUCCESS = "GET_PRODUCTS_SUCCESS";
+export const GET_PRODUCTS_FAILURE = "GET_PRODUCTS_SUCCESS";
+export const GET_CART_ITEMS_REQUEST = "GET_CART_ITEMS_REQUEST";
+export const GET_CART_ITEMS_SUCCESS = "GET_CART_ITEMS_SUCCESS";
+export const GET_CART_ITEMS_FAILURE = "GET_CART_ITEMS_FAILURE";
+export const ADD_ITEMS_TO_CART_REQUEST = "ADD_ITEMS_TO_CART_REQUEST";
+export const ADD_ITEMS_TO_CART_SUCCESS = "ADD_ITEMS_TO_CART_SUCCESS";
+export const ADD_ITEMS_TO_CART_FAILURE = "ADD_ITEMS_TO_CART_FAILURE";
diff --git a/src/client/redux/actions/index.js b/src/client/redux/actions/index.js
new file mode 100644
index 0000000..e6e82b8
--- /dev/null
+++ b/src/client/redux/actions/index.js
@@ -0,0 +1,61 @@
+import * as actionTypes from "../actionTypes";
+import { transformProductsApi } from "../transformers/transformProductsApi";
+import { transformGetCartItemsApi } from "../transformers/transformGetCartItemsApi";
+
+export const getProducts = productId => {
+ return dispatch => {
+ const apiUrl = productId ? `/api/products/${productId}` : "/api/products";
+ return dispatch({
+ type: actionTypes.GET_PRODUCTS,
+ promise: fetch(apiUrl).then(async response => {
+ const responseData = await response.json();
+ if (response.ok) {
+ return transformProductsApi(responseData);
+ } else {
+ Promise.reject("Something went wrong");
+ }
+ })
+ });
+ };
+};
+
+export const getCartItems = () => {
+ return dispatch => {
+ return dispatch({
+ type: actionTypes.GET_CART_ITEMS,
+ promise: fetch("/api/cart-items").then(async response => {
+ const responseData = await response.json();
+ if (response.ok) {
+ return transformGetCartItemsApi(responseData);
+ } else {
+ Promise.reject("Something went wrong");
+ }
+ })
+ });
+ };
+};
+
+export const addItemsToCart = product => {
+ const data = {
+ productId: product.id
+ };
+ return dispatch => {
+ return dispatch({
+ type: actionTypes.ADD_ITEMS_TO_CART,
+ promise: fetch("/api/cart-items", {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: {
+ "Content-Type": "application/json"
+ }
+ }).then(async response => {
+ const responseData = await response.json();
+ if (response.ok) {
+ return responseData;
+ } else {
+ return Promise.reject("Something went wrong");
+ }
+ })
+ });
+ };
+};
diff --git a/src/client/redux/reducers/cartItems.js b/src/client/redux/reducers/cartItems.js
new file mode 100644
index 0000000..0d111ab
--- /dev/null
+++ b/src/client/redux/reducers/cartItems.js
@@ -0,0 +1,53 @@
+import * as actionTypes from "../actionTypes";
+import { handle } from "redux-pack";
+
+const initialState = {
+ byId: {},
+ ids: [],
+ isLoading: false,
+ isError: false,
+ errorMsg: ""
+};
+
+export default function cartItemsReducer(state = initialState, action) {
+ switch (action.type) {
+ case actionTypes.GET_CART_ITEMS:
+ return handle(state, action, {
+ start: s => ({ ...s, isLoading: true }),
+ success: s => ({
+ ...s,
+ ...action.payload,
+ isLoading: false
+ }),
+ failure: s => ({
+ ...s,
+ isLoading: false,
+ isError: true,
+ errorMsg: action.payload
+ })
+ });
+
+ case actionTypes.ADD_ITEMS_TO_CART:
+ return handle(state, action, {
+ start: s => ({ ...s, isLoading: true }),
+ success: s => ({
+ ...s,
+ byId: {
+ ...state.byId,
+ [action.payload.id]: action.payload
+ },
+ ids: [...state.ids, action.payload.id],
+ isLoading: false
+ }),
+ failure: s => ({
+ ...s,
+ isLoading: false,
+ isError: true,
+ errorMsg: action.payload
+ })
+ });
+
+ default:
+ return state;
+ }
+}
diff --git a/src/client/redux/reducers/index.js b/src/client/redux/reducers/index.js
new file mode 100644
index 0000000..2b6075b
--- /dev/null
+++ b/src/client/redux/reducers/index.js
@@ -0,0 +1,8 @@
+import { combineReducers } from "redux";
+import products from "./products";
+import cartItems from "./cartItems";
+
+export default combineReducers({
+ products,
+ cartItems
+});
diff --git a/src/client/redux/reducers/products.js b/src/client/redux/reducers/products.js
new file mode 100644
index 0000000..599b945
--- /dev/null
+++ b/src/client/redux/reducers/products.js
@@ -0,0 +1,40 @@
+import * as actionTypes from "../actionTypes";
+import { handle } from "redux-pack";
+import merge from "lodash/merge";
+
+const initialState = {
+ byId: {},
+ ids: [],
+ isLoading: false,
+ isError: false,
+ errorMsg: ""
+};
+
+export default function productsReducer(state = initialState, action) {
+ switch (action.type) {
+ case actionTypes.GET_PRODUCTS:
+ return handle(state, action, {
+ start: s => ({
+ ...s,
+ byId: {},
+ ids: [],
+ isLoading: true
+ }),
+ success: s => ({
+ ...s,
+ byId: merge({}, s.byId, action.payload.byId),
+ ids: [...s.ids, action.payload.ids],
+ isLoading: false
+ }),
+ failure: s => ({
+ ...s,
+ isLoading: false,
+ isError: true,
+ errorMsg: action.payload
+ })
+ });
+
+ default:
+ return state;
+ }
+}
diff --git a/src/client/redux/transformers/transformGetCartItemsApi.js b/src/client/redux/transformers/transformGetCartItemsApi.js
new file mode 100644
index 0000000..03647f6
--- /dev/null
+++ b/src/client/redux/transformers/transformGetCartItemsApi.js
@@ -0,0 +1,10 @@
+export const transformGetCartItemsApi = data => ({
+ byId: data.reduce(
+ (obj, cartItem) => ({
+ ...obj,
+ [cartItem.id]: cartItem
+ }),
+ {}
+ ),
+ ids: data.map(cartItem => cartItem.id)
+});
diff --git a/src/client/redux/transformers/transformProductsApi.js b/src/client/redux/transformers/transformProductsApi.js
new file mode 100644
index 0000000..0e3980e
--- /dev/null
+++ b/src/client/redux/transformers/transformProductsApi.js
@@ -0,0 +1,10 @@
+export const transformProductsApi = data => ({
+ byId: data.reduce(
+ (obj, product) => ({
+ ...obj,
+ [product.id]: product
+ }),
+ {}
+ ),
+ ids: data.map(product => product.id)
+});
diff --git a/src/client/store.js b/src/client/store.js
new file mode 100644
index 0000000..08e9bfd
--- /dev/null
+++ b/src/client/store.js
@@ -0,0 +1,14 @@
+import { createStore, applyMiddleware, compose } from "redux";
+import thunk from "redux-thunk";
+import { middleware as reduxPack } from "redux-pack";
+import rootReducer from "./redux/reducers";
+
+export default function configureStore() {
+ const composeEnhancers =
+ window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+ const store = createStore(
+ rootReducer,
+ composeEnhancers(applyMiddleware(thunk, reduxPack))
+ );
+ return store;
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..ec1f0e8
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,16 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import App from "./client/components/App";
+import "bootstrap/dist/css/bootstrap.min.css";
+import { BrowserRouter, Route } from "react-router-dom";
+import { Provider } from "react-redux";
+import configureStore from "./client/store";
+
+ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById("root")
+);
diff --git a/src/server/connectors/index.js b/src/server/connectors/index.js
index c9a4fce..63195c3 100644
--- a/src/server/connectors/index.js
+++ b/src/server/connectors/index.js
@@ -2,6 +2,7 @@ let products = [
{
id: 1,
name: "Macbook",
+ brand: "Apple",
description: "Latest Macbook with 16GB ram and Quad core processor",
price: 65000,
url: "/img/macbook.jpeg"
@@ -9,6 +10,15 @@ let products = [
{
id: 2,
name: "Keyboard",
+ brand: "Apple",
+ description: "Ergonomic keyboard",
+ price: 30000,
+ url: "/img/keyboard.jpeg"
+ },
+ {
+ id: 3,
+ name: "Keyboard",
+ brand: "Samsung",
description: "Ergonomic keyboard",
price: 3000,
url: "/img/keyboard.jpeg"
@@ -29,8 +39,20 @@ export function getProducts() {
return products;
}
-export function getProduct(id) {
- return products.find(product => product.id === id);
+export function getProductById(id) {
+ return [products.find(product => product.id === id)];
+}
+
+export function getProductByBrand(brand) {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve(
+ products.filter(
+ product => product.brand.toLowerCase() === brand.toLowerCase()
+ )
+ );
+ }, 5000);
+ });
}
export function getCartItem(id) {
@@ -41,16 +63,20 @@ export function getCartItems() {
return cartItems;
}
-export function addToCart(args) {
- if (cartItems.find(c => c.productId === parseInt(args.productId, 10))) {
+export function addToCart({ productId }) {
+ if (cartItems.find(c => c.productId === parseInt(productId, 10))) {
throw new Error("Product already in cart");
}
const newCartItem = {
id: cartItems.length + 1,
- productId: parseInt(args.productId, 10)
+ product: products.find(p => p.id === productId)
};
cartItems.push(newCartItem);
- return newCartItem;
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve(newCartItem);
+ }, 5000);
+ });
}
export function deleteCartItem(args) {
diff --git a/src/server/index.js b/src/server/index.js
index 65ac35b..3df183d 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -4,7 +4,8 @@ import morgan from "morgan";
import {
getUser,
getProducts,
- getProduct,
+ getProductById,
+ getProductByBrand,
getCartItems,
getCartItem,
addToCart,
@@ -13,7 +14,7 @@ import {
const PORT = 8000;
const app = express();
-app.use(bodyParser.urlencoded({ extended: false }));
+app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(
@@ -31,9 +32,9 @@ app.use(
})
);
-app.use(function(req, res, next) {
- setTimeout(next, 500);
-});
+// app.use(function(req, res, next) {
+// setTimeout(next, 500);
+// });
app.get("/api/user", function(req, res) {
res.json(getUser());
@@ -43,9 +44,13 @@ app.get("/api/products", function(req, res) {
res.json(getProducts());
});
-app.get("/api/products/:id", function(req, res) {
+app.get("/api/products/:id(\\d+)/", function(req, res) {
const id = parseInt(req.params.id, 10);
- res.json(getProduct(id));
+ res.json(getProductById(id));
+});
+
+app.get("/api/products/:brand(\\w+)/", function(req, res) {
+ getProductByBrand(req.params.brand).then(response => res.json(response));
});
app.get("/api/cart-items", function(req, res) {
@@ -58,7 +63,7 @@ app.get("/api/cart-items/:id", function(req, res) {
});
app.post("/api/cart-items", function(req, res) {
- res.json(addToCart(req.body));
+ addToCart(req.body).then(response => res.json(response));
});
app.post("/api/cart-items/:id", function(req, res) {
diff --git a/yarn.lock b/yarn.lock
index 824a933..03de3f9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2238,6 +2238,10 @@ delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+deline@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/deline/-/deline-1.0.4.tgz#6c05c87836926e1a1c63e47882f3d2eb2c6f14c9"
+
depd@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
@@ -3543,7 +3547,7 @@ hoek@4.x.x:
version "4.2.1"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
-hoist-non-react-statics@^2.3.0:
+hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
@@ -3800,7 +3804,7 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
-invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
@@ -4908,6 +4912,10 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
+lodash-es@^4.17.5:
+ version "4.17.10"
+ resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05"
+
lodash._reinterpolate@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -4965,6 +4973,10 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+lodash@4.17.10:
+ version "4.17.10"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
+
"lodash@>=3.5 <5", lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
@@ -6480,6 +6492,17 @@ react-portal@^4.1.2:
dependencies:
prop-types "^15.5.8"
+react-redux@^5.0.7:
+ version "5.0.7"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
+ dependencies:
+ hoist-non-react-statics "^2.5.0"
+ invariant "^2.0.0"
+ lodash "^4.17.5"
+ lodash-es "^4.17.5"
+ loose-envify "^1.1.0"
+ prop-types "^15.6.0"
+
react-router-dom@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d"
@@ -6685,6 +6708,25 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^0.4.2"
+redux-pack@0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/redux-pack/-/redux-pack-0.1.5.tgz#1973b26ef749dfc020e8a61d54cf3e55ec8ae4cd"
+ dependencies:
+ deline "^1.0.4"
+ invariant "^2.2.2"
+ uuid "^3.0.1"
+
+redux-thunk@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
+
+redux@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03"
+ dependencies:
+ loose-envify "^1.1.0"
+ symbol-observable "^1.2.0"
+
regenerate@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
@@ -7604,6 +7646,10 @@ symbol-observable@^0.2.2:
version "0.2.4"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40"
+symbol-observable@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
+
symbol-tree@^3.2.1, symbol-tree@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
@@ -7989,7 +8035,7 @@ uuid@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
-uuid@^3.0.0, uuid@^3.1.0:
+uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"