<repo_name>YuraPelehatiy/internet-store-app<file_sep>/src/layouts/App.js
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { routes } from '../routes';
import Desktop from './Desktop';
import AuthPage from '../pages/Auth/AuthPage';


const App = () => (
    <Switch>
        <Route path={routes.authLogin} component={AuthPage} />
        <Route path={routes.authRegister} component={AuthPage} />
        <Route path={routes.authRemember} component={AuthPage} />
        <Route component={Desktop} />
    </Switch>
);


export default App;<file_sep>/src/components/ProductLinkAdmin/ProductLinkAdmin.js
import React from 'react';
import { Link } from 'react-router-dom';
import { formatRoute } from 'react-router-named-routes';
import { routes } from '../../routes';
import s from './ProductLinkAdmin.module.css';
import ActionButton from '../ActionButton/ActionButton';

const ProductLinkAdmin = ({
    id,
    title,
    showModalToUpdateProduct,
    showModalToRemoveProduct,
    users,
}) => (
    <div className={s.ProductLinkAdmin}>
        <Link to={formatRoute((users && routes.adminUser) || routes.adminProduct, { id })}>
            {title}
        </Link>
        <div className={s.ProductLinkAdminButtons}>
            <ActionButton
                mediumSize
                onClick={() => showModalToUpdateProduct(id)}
            >
                Edit
            </ActionButton>
            <ActionButton
                mediumSize
                onClick={() => showModalToRemoveProduct(id)}
            >
                Remove
            </ActionButton>
        </div>
    </div>
);

export default ProductLinkAdmin;<file_sep>/src/modules/products/productsSelectors.js
import { createSelector } from 'reselect';

const getProductsIds = state => state.products.products;
const getProductById = (state, id) => state.entities.products[id];
const getProductsEntities = state => state.entities.products;

export const getProducst = createSelector(
    [getProductsIds, getProductsEntities],
    (products, entities) => products.map(id => entities[id]),
);

export const getProduct = createSelector(getProductById, result => result);<file_sep>/src/pages/NotFound/NotFoundPage.js
import React from 'react';

const NotFoundPage = () => (
    <h2>Page is Not Found</h2>
);

export default NotFoundPage;<file_sep>/src/modules/products/productsReducer.js
import { handleActions } from 'redux-actions';
import * as constants from './productsConstants';

const initialState = {
    products: [],
    isLoading: false,
    error: null,
    limit: 12,
    page: 0,
    offset: 0,
};

export default handleActions(
    {
        // Fetch all products
        [constants.FETCH_PRODUCTS_START]: state => ({
            ...state,
            isLoading: true,
            error: null,
        }),
        [constants.FETCH_PRODUCTS_OK]: (state, action) => ({
            ...state,
            isLoading: false,
            products: action.payload.ids,
        }),
        [constants.FETCH_PRODUCTS_ERROR]: (state, action) => ({
            ...state,
            isLoading: false,
            error: action.payload,
        }),

        // Get one product
        [constants.GET_PRODUCT_START]: state => ({
            ...state,
            isLoading: true,
            error: null,
        }),
        [constants.GET_PRODUCT_OK]: (state, action) => ({
            ...state,
            isLoading: false,
            products: [action.payload.id[0]].concat(state.products),
        }),
        [constants.GET_PRODUCT_ERROR]: (state, action) => ({
            ...state,
            isLoading: false,
            error: action.payload,
        }),

        [constants.SET_PAGE]: (state, action) => ({
            ...state,
            page: action.payload.page,
            offset: action.payload.page * state.limit,
        }),
    },
    initialState,
);<file_sep>/src/modules/app/appOperations.js
import * as Api from '../../api/Api';
import * as appActions from './appActions';

export const init = () => async (dispatch) => {
    try {
        Api.initApi();

        const res = await Api.User.getCurrent();

        dispatch(appActions.addUser({
            user: res.data.user,
        }));
    } catch (err) {
        Api.setToken(undefined);
    }
};

export const logout = () => (dispatch) => {
    Api.removeToken();

    dispatch(appActions.removeUser());
};

export const login = values => async (dispatch) => {
    try {
        const res = await Api.Auth.login(values);

        Api.setToken(res.data.token);

        const resUser = await Api.User.getCurrent();

        dispatch(appActions.addUser({
            user: resUser.data.user,
        }));
    } catch (err) {
        throw new Error();
    }
};

export const register = values => async () => {
    try {
        const res = await Api.Auth.register(values);

        if (!res.data.success) {
            throw new Error();
        }
    } catch (err) {
        throw new Error();
    }
};

export const remember = values => async () => {
    try {
        const res = await Api.Auth.remember(values);

        if (!res.data.success) {
            throw new Error();
        }
    } catch (err) {
        throw new Error();
    }
};<file_sep>/src/modules/app/appReducer.js
import { handleActions } from 'redux-actions';
import * as constants from './appConstants';

const initialState = {
    user: {},
};

export default handleActions(
    {
        [constants.ADD_USER]: (state, action) => ({
            ...state,
            user: action.payload.user,
        }),
        [constants.REMOVE_USER]: state => ({
            ...state,
            user: {},
        }),
    },
    initialState,
);<file_sep>/src/modules/cart/cartConstants.js
export const ADD = 'cart/ADD';
export const REMOVE = 'cart/REMOVE';

export const FETCH_PRODUCTS_START = 'cart/FETCH_PRODUCTS_START';
export const FETCH_PRODUCTS_OK = 'cart/FETCH_PRODUCTS_OK';
export const FETCH_PRODUCTS_ERROR = 'cart/FETCH_PRODUCTS_ERROR';

export const INCREASE = 'cart/INCREASE';
export const DECREASE = 'cart/DECREASE';
export const ENTER_VALUE = 'cart/ENTER_VALUE';<file_sep>/src/modules/cart/cartActions.js
import { createAction } from 'redux-actions';
import * as constants from './cartConstants';

export const add = createAction(constants.ADD);
export const remove = createAction(constants.REMOVE);

export const fetchProductsStart = createAction(constants.FETCH_PRODUCTS_START);
export const fetchProductsOk = createAction(constants.FETCH_PRODUCTS_OK);
export const fetchProductsError = createAction(constants.FETCH_PRODUCTS_ERROR);

export const increase = createAction(constants.INCREASE);
export const decrease = createAction(constants.DECREASE);
export const enterValue = createAction(constants.ENTER_VALUE);<file_sep>/src/modules/cart/cartOperation.js
import { normalize } from 'normalizr';
import * as schemes from '../../api/schemes';
import * as actions from './cartActions';
import * as Api from '../../api/Api';

export const fetchProducts = () => async (dispatch, getState) => {
    try {
        const products = getState().entities.products;
        const items = getState().cart.items;

        if (Object.keys(products).length > 1 || Object.keys(items).length === 0) {
            return;
        }

        const ids = Object.keys(items);

        dispatch(actions.fetchProductsStart());

        const res = await Api.Cart.getProductsByIds(ids);
        const { entities } = normalize(res.data, schemes.ProductCollection);

        dispatch(actions.fetchProductsOk({
            entities,
        }));
    } catch (err) {
        dispatch(actions.fetchProductsError(err.message));
    }
};<file_sep>/src/components/InputForm/InputForm.js
import React from 'react';
import classNames from 'classnames/bind';
import s from './InputForm.module.css';

const cx = classNames.bind(s);

const InputForm = ({
    meta,
    ...props
}) => (
    <div className={s.InputContainer}>
        <input
            {...props}
            className={cx({
                InputField: true,
                InputFieldError: (meta && meta.error && meta.touched),
            })}
        />
        {meta && meta.error && meta.touched && <span className={s.ErrorField}>{meta.error}</span>}
    </div>
);

export default InputForm;<file_sep>/src/modules/cart/cartSelectors.js
import { createSelector } from 'reselect';

const getProductsItems = (state, isLoading) => {
    // We use this condition and the isLoading parameter to force
    // the getProducts selector to recalculate after fetching data
    // from the server.

    if (isLoading) {
        return [];
    }
    return state.cart.items;
};

const getProductsEntities = state => state.entities.products;

export const getProducts = createSelector(
    [getProductsItems, getProductsEntities],
    (items, entities) => Object.keys(items).map((id) => {
        if (entities[id] === undefined) {
            return ({
                id,
                title: 'Loading...',
                price: 0,
            });
        }

        return entities[id];
    }),
);

export const getTotalPrice = createSelector(
    [getProducts, getProductsItems],
    (products, items) => (
        products.reduce((acc, product) => acc + product.price * items[product.id].count, 0)
    ),
);

export const getCountItems = createSelector(
    [getProductsItems],
    (items) => {
        const ids = Object.keys(items);
        return ids.reduce((acc, id) => acc + items[id].count, 0);
    },
);<file_sep>/src/pages/Cart/CartPageComponent.js
import React from 'react';
import s from './CartPage.module.css';
import ActionButton from '../../components/ActionButton/ActionButton';
import CartList from './CartList/CartList';

const CartPageComponent = ({
    products,
    renderProductLink,
    countTotalPrice,
    itemsCart,
}) => (
    <div className={s.CartPage}>
        <h2 className={s.CartPageTop}>Cart list</h2>
        <hr />
        <CartList
            products={products}
            renderProductLink={renderProductLink}
            itemsCart={itemsCart}
        />
        <hr />
        <div className={s.CartPageBottom}>
            <div className={s.CartPageTotalPrice}>
                Total Price: {countTotalPrice}
            </div>
            <ActionButton>Checkout</ActionButton>
        </div>

    </div>
);

export default CartPageComponent;<file_sep>/src/pages/ProductAdmin/ProductPageAdminContainer.js
import { connect } from 'react-redux';
import {
    compose, lifecycle, branch, renderComponent, mapProps,
} from 'recompose';
import ProductPageAdminComponent from './ProductPageAdminComponent';
import * as adminOperations from '../../modules/admin/adminOperations';
import * as adminSelectors from '../../modules/admin/adminSelectors';
import Loader from '../../components/Loader/Loader';
import ErrorLoadign from '../../components/ErrorLoading/ErrorLoading';

const mapStateToProps = (state, props) => ({
    product: adminSelectors.getProduct(state, props.match.params.id),
    isLoading: state.admin.isLoading,
    isError: !!state.admin.error,
    error: state.admin.error,
});

const mapStateToDispatch = {
    getProduct: adminOperations.getProduct,
    removeProduct: adminOperations.removeProduct,
    updateProduct: adminOperations.updateProduct,
};

export default compose(
    connect(
        mapStateToProps,
        mapStateToDispatch,
    ),
    lifecycle({
        componentDidMount() {
            this.props.getProduct(this.props.match.params.id);
        },
    }),
    branch(
        props => props.isLoading,
        renderComponent(Loader),
        branch(
            props => props.isError,
            renderComponent(ErrorLoadign),
        ),
    ),
    mapProps(props => ({
        updateProduct: props.updateProduct,
        removeProduct: props.removeProduct,
        product: props.product,
        history: props.history,
    })),
)(ProductPageAdminComponent);<file_sep>/src/modules/app/appActions.js
import { createAction } from 'redux-actions';
import * as constants from './appConstants';

export const addUser = createAction(constants.ADD_USER);
export const removeUser = createAction(constants.REMOVE_USER);<file_sep>/src/pages/Home/ProductList/ProductListContainer.js
import React from 'react';
import { connect } from 'react-redux';
import {
    compose, branch, renderComponent, mapProps,
} from 'recompose';
import * as productsSelectors from '../../../modules/products/productsSelectors';
import * as cartActions from '../../../modules/cart/cartActions';
import ProdutcListComponent from './ProdutcListComponent';
import ProductLink from '../../../components/ProductLink/ProductLink';
import Loader from '../../../components/Loader/Loader';
import ErrorLoadign from '../../../components/ErrorLoading/ErrorLoading';

const mapStateToProps = state => ({
    products: productsSelectors.getProducst(state),
    isLoading: state.products.isLoading,
    isError: !!state.products.error,
    error: state.products.error,
});

const mapStateToDispatch = {
    addItemToCart: cartActions.add,
};

export default compose(
    connect(
        mapStateToProps,
        mapStateToDispatch,
    ),
    branch(
        props => props.isLoading,
        renderComponent(Loader),
        branch(
            props => props.isError,
            renderComponent(ErrorLoadign),
        ),
    ),
    mapProps(props => ({
        renderProductLink: item => (
            <ProductLink
                key={item.id}
                id={item.id}
                title={item.title}
                image={item.image}
                price={item.price}
                onActionButtonClick={props.addItemToCart}
                actionButtonTitle="Add to Cart"
            />
        ),
        products: props.products,
    })),
)(ProdutcListComponent);<file_sep>/src/modules/admin/adminConstants.js
export const SET_PAGE_PRODUCTS = 'admin/SET_PAGE_PRODUCTS';
export const SET_PAGE_USERS = 'admin/SET_PAGE_USERS';

//--------------------------------------------------------------------------
//                        Constants for products
//--------------------------------------------------------------------------

export const FETCH_PRODUCTS_START = 'admin/FETCH_PRODUCTS_START';
export const FETCH_PRODUCTS_OK = 'admin/FETCH_PRODUCTS_OK';
export const FETCH_PRODUCTS_ERROR = 'admin/FETCH_PRODUCTS_ERROR';

export const CREATE_PRODUCT_START = 'admin/CREATE_PRODUCT_START';
export const CREATE_PRODUCT_OK = 'admin/CREATE_PRODUCT_OK';
export const CREATE_PRODUCT_ERROR = 'admin/CREATE_PRODUCT_ERROR';

export const UPDATE_PRODUCT_START = 'admin/UPDATE_PRODUCT_START';
export const UPDATE_PRODUCT_OK = 'admin/UPDATE_PRODUCT_OK';
export const UPDATE_PRODUCT_ERROR = 'admin/UPDATE_PRODUCT_ERROR';

export const REMOVE_PRODUCT_START = 'admin/REMOVE_PRODUCT_START';
export const REMOVE_PRODUCT_OK = 'admin/REMOVE_PRODUCT_OK';
export const REMOVE_PRODUCT_ERROR = 'admin/REMOVE_PRODUCT_ERROR';

export const GET_PRODUCT_START = 'admin/GET_PRODUCT_START';
export const GET_PRODUCT_OK = 'admin/GET_PRODUCT_OK';
export const GET_PRODUCT_ERROR = 'admin/GET_PRODUCT_ERROR';


//--------------------------------------------------------------------------
//                        Constants for users
//--------------------------------------------------------------------------

export const FETCH_USERS_START = 'admin/FETCH_USERS_START';
export const FETCH_USERS_OK = 'admin/FETCH_USERS_OK';
export const FETCH_USERS_ERROR = 'admin/FETCH_USERS_ERROR';

export const UPDATE_USER_START = 'admin/UPDATE_USER_START';
export const UPDATE_USER_OK = 'admin/UPDATE_USER_OK';
export const UPDATE_USER_ERROR = 'admin/UPDATE_USER_ERROR';

export const REMOVE_USER_START = 'admin/REMOVE_USER_START';
export const REMOVE_USER_OK = 'admin/REMOVE_USER_OK';
export const REMOVE_USER_ERROR = 'admin/REMOVE_USER_ERROR';

export const GET_USER_START = 'admin/GET_USER_START';
export const GET_USER_OK = 'admin/GET_USER_OK';
export const GET_USER_ERROR = 'admin/GET_USER_ERROR';<file_sep>/src/pages/Auth/Remember/RememberPage.js
import React from 'react';
import { Form, Field } from 'react-final-form';
import { FORM_ERROR } from 'final-form';
import { connect } from 'react-redux';
import * as appOperations from '../../../modules/app/appOperations';
import InputForm from '../../../components/InputForm/InputForm';
import ActionButton from '../../../components/ActionButton/ActionButton';
import ErrorSubmiting from '../../../components/ErrorSubmiting/ErrorSubmiting';

const validate = (values) => {
    const errors = {};

    if (!values.email || values.email.trim().length === 0 || !values.email.includes('@')) {
        errors.email = 'Please enter a valid email address';
    }

    return errors;
};

const RememberPage = ({ remember }) => {
    const onSubmit = async (values, form) => { // eslint-disable-line
        try {
            await remember(values);
            form.reset();
        } catch (err) {
            return {
                [FORM_ERROR]: 'Something went wrong',
            };
        }
    };

    return (
        <div>
            <Form
                onSubmit={onSubmit}
                validate={validate}
                render={({ handleSubmit, submitError, submitSucceeded }) => (
                    <>
                        <Field name="email">
                            {({ input, meta }) => (
                                <InputForm {...input} type="text" placeholder="Email" meta={meta} />
                            )}
                        </Field>
                        <div>
                            <ActionButton onClick={handleSubmit}>Remember</ActionButton>
                        </div>
                        {submitError && <ErrorSubmiting>{submitError}</ErrorSubmiting>}
                        {submitSucceeded && <div>We have sent instructions for your email</div>}
                    </>
                )}
            />
        </div>
    );
};

const mapStateToDispatch = {
    remember: appOperations.remember,
};


export default connect(
    undefined,
    mapStateToDispatch,
)(RememberPage);<file_sep>/src/components/ProductForm/ProductForm.js
import React from 'react';
import { Form, Field } from 'react-final-form';
import { FORM_ERROR } from 'final-form';
import InputForm from '../InputForm/InputForm';
import ActionButton from '../ActionButton/ActionButton';
import TextAreaForm from '../TextAreaForm/TextAreaForm';
import ErrorSubmiting from '../ErrorSubmiting/ErrorSubmiting';

const validate = (values) => {
    const errors = {};

    if (!values.title || values.title.trim().length === 0) {
        errors.title = 'Requared';
    }
    if (!values.description || values.description.trim().length === 0) {
        errors.description = 'Requared';
    }
    if (!values.image || values.image.trim().length === 0) {
        errors.image = 'Requared';
    }
    if (!values.price) {
        errors.price = 'Requared';
    }

    return errors;
};

const ProductForm = ({
    onClose,
    onSubmitAction,
    onSubmitButtonTitle,
    product,
}) => {
    const onSubmit = async (values, form) => { // eslint-disable-line
        try {
            await onSubmitAction(values);

            form.reset();
            onClose();
        } catch (err) {
            return {
                [FORM_ERROR]: 'Something went wrong',
            };
        }
    };

    return (
        <>
            <Form
                initialValues={{ ...product }}
                onSubmit={onSubmit}
                validate={validate}
                render={({
                    handleSubmit, submitError, submitting, submitSucceeded,
                }) => (
                    <>
                        <Field name="title">
                            {({ input, meta }) => (
                                <InputForm {...input} type="text" placeholder="Title" meta={meta} />
                            )}
                        </Field>
                        <Field name="description">
                            {({ input, meta }) => (
                                <TextAreaForm {...input} placeholder="Description" meta={meta} />
                            )}
                        </Field>
                        <Field name="image">
                            {({ input, meta }) => (
                                <InputForm {...input} type="text" placeholder="Image URL" meta={meta} />
                            )}
                        </Field>
                        <Field name="price">
                            {({ input, meta }) => (
                                <InputForm {...input} type="number" placeholder="Price" meta={meta} />
                            )}
                        </Field>
                        <div>
                            <ActionButton onClick={handleSubmit}>
                                {onSubmitButtonTitle}
                            </ActionButton>
                        </div>
                        {submitting && <h2>Submiting...</h2>}
                        {submitSucceeded && <h2>Complete</h2>}
                        {submitError && <ErrorSubmiting>{submitError}</ErrorSubmiting>}
                    </>
                )}
            />
        </>
    );
};

export default ProductForm;<file_sep>/src/pages/Home/ProductList/ProdutcListComponent.js
import React from 'react';
import s from './ProductList.module.css';

const ProdutcListComponent = ({
    products,
    renderProductLink,
}) => (
    <div className={s.ProductList}>
        {products.map(item => renderProductLink(item))}
    </div>
);

export default ProdutcListComponent;<file_sep>/src/pages/Auth/Login/LoginPage.js
import React from 'react';
import { Form, Field } from 'react-final-form';
import { FORM_ERROR } from 'final-form';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import * as appOperations from '../../../modules/app/appOperations';
import { routes } from '../../../routes';
import InputForm from '../../../components/InputForm/InputForm';
import ActionButton from '../../../components/ActionButton/ActionButton';
import ErrorSubmiting from '../../../components/ErrorSubmiting/ErrorSubmiting';

const validate = (values) => {
    const errors = {};

    if (!values.email || values.email.trim().length < 0 || !values.email.includes('@')) {
        errors.email = 'Please enter a valid email address';
    }

    if (!values.password || values.password.trim().length < 8) {
        errors.password = 'Please enter a valid password, password must be 8 or more symbols';
    }

    return errors;
};

const LoginPage = ({ login }) => {
    const onSubmit = async (values, form) => { // eslint-disable-line
        try {
            await login(values);

            form.reset();
        } catch (err) {
            return {
                [FORM_ERROR]: 'Wrong email or password',
            };
        }
    };

    return (
        <div>
            <Form
                onSubmit={onSubmit}
                validate={validate}
                render={({ handleSubmit, submitError, submitSucceeded }) => (
                    <>
                        <Field name="email">
                            {({ input, meta }) => (
                                <InputForm {...input} type="text" placeholder="Email" meta={meta} />
                            )}
                        </Field>
                        <Field name="password">
                            {({ input, meta }) => (
                                <InputForm {...input} type="password" placeholder="Password" meta={meta} />
                            )}
                        </Field>
                        <div>
                            <ActionButton onClick={handleSubmit}>Login</ActionButton>
                        </div>
                        {submitError && <ErrorSubmiting>{submitError}</ErrorSubmiting>}
                        {submitSucceeded && <Redirect to={routes.home} />}
                    </>
                )}
            />
        </div>
    );
};

const mapStateToDispatch = {
    login: appOperations.login,
};

export default connect(
    undefined,
    mapStateToDispatch,
)(LoginPage);<file_sep>/src/modules/admin/adminOperations.js
import { normalize } from 'normalizr';
import * as schemes from '../../api/schemes';
import * as actions from './adminActions';
import * as Api from '../../api/Api';

// Poructs operations
export const fetchProducts = () => async (dispatch, getState) => {
    try {
        const offset = getState().admin.offsetProducts;
        const limit = getState().admin.limit;

        dispatch(actions.fetchProductsStart());

        const res = await Api.AdminProducts.fetchProducts(offset, limit);
        const { result: ids, entities } = normalize(res.data, schemes.AdminProductCollection);

        dispatch(actions.fetchProductsOk({
            ids,
            entities,
        }));
    } catch (err) {
        dispatch(actions.fetchProductsError(err.message));
    }
};

export const createProduct = newProduct => async (dispatch) => {
    try {
        dispatch(actions.createProductStart());

        const res = await Api.AdminProducts.createProduct(newProduct);
        const { result: ids, entities } = normalize(res.data, schemes.AdminProductCollection);

        dispatch(actions.createProductOk({
            ids,
            entities,
        }));
    } catch (err) {
        dispatch(actions.createProductError(err.message));
    }
};

export const updateProduct = productData => async (dispatch) => {
    try {
        dispatch(actions.updateProductStart());

        const res = await Api.AdminProducts.updateProductById(productData.id, productData);

        const { result: ids, entities } = normalize(res.data, schemes.AdminProductCollection);

        dispatch(actions.updateProductOk({
            ids,
            entities,
        }));
    } catch (err) {
        dispatch(actions.updateProductError(err.message));
    }
};

export const removeProduct = id => async (dispatch) => {
    try {
        dispatch(actions.removeProductStart());

        const res = await Api.AdminProducts.removeProductById(id);

        if (res && res.data && res.data.success) {
            const ids = [id];
            dispatch(actions.removeProductOk({
                ids,
            }));
        }
    } catch (err) {
        dispatch(actions.removeProductError(err.message));
    }
};

export const getProduct = id => async (dispatch, getState) => {
    try {
        const product = getState().entities.products[id];

        if (product) {
            return;
        }

        dispatch(actions.getProductStart());

        const res = await Api.AdminProducts.getProductsById(id);
        const { result, entities } = normalize(res.data, schemes.AdminProductCollection);

        dispatch(actions.getProductOk({
            id: result,
            entities,
        }));
    } catch (err) {
        dispatch(actions.getProductError(err.message));
    }
};

// Users operations
export const fetchUsers = () => async (dispatch, getState) => {
    try {
        const offset = getState().admin.offsetUsers;
        const limit = getState().admin.limit;

        dispatch(actions.fetchUsersStart());

        const res = await Api.AdminUsers.fetchUsers(offset, limit);
        const { result: ids, entities } = normalize(res.data, schemes.AdminUserCollection);

        dispatch(actions.fetchUsersOk({
            ids,
            entities,
        }));
    } catch (err) {
        dispatch(actions.fetchUsersError(err.message));
    }
};

export const updateUser = userData => async (dispatch) => {
    try {
        dispatch(actions.updateUserStart());

        const res = await Api.AdminUsers.updateUserById(userData.id, userData);

        const { result: ids, entities } = normalize(res.data, schemes.AdminUserCollection);

        dispatch(actions.updateUserOk({
            ids,
            entities,
        }));
    } catch (err) {
        dispatch(actions.updateUserError(err.message));
    }
};

export const removeUser = id => async (dispatch) => {
    try {
        dispatch(actions.removeUserStart());

        const res = await Api.AdminUsers.removeUserById(id);

        if (res && res.data && res.data.success) {
            const ids = [id];
            dispatch(actions.removeUserOk({
                ids,
            }));
        }
    } catch (err) {
        dispatch(actions.removeUserError(err.message));
    }
};

export const getUser = id => async (dispatch, getState) => {
    try {
        const user = getState().entities.users[id];

        if (user) {
            return;
        }

        dispatch(actions.getUserStart());

        const res = await Api.AdminUsers.getUserById(id);
        const { result, entities } = normalize(res.data, schemes.AdminUserCollection);

        dispatch(actions.getUserOk({
            id: result,
            entities,
        }));
    } catch (err) {
        dispatch(actions.getUserError(err.message));
    }
};<file_sep>/src/pages/Admin/UserListPageAdmin/UserListPageAdminComponent.js
import React from 'react';
import PaginatePanel from '../../../components/PaginatePanel/PaginatePanel';
import UserListAdminContainer from '../UserListAdmin/UserListAdminContainer';

const UserListPageAdminComponent = ({
    handleOnPageChange,
    selectedPage,
}) => (
    <div>
        <UserListAdminContainer />
        <PaginatePanel
            onPageChange={handleOnPageChange}
            initialPage={selectedPage}
            pageCount={10}
            pageRangeDisplayed={5}
            marginRangeDisplayed={3}
        />
    </div>
);

export default UserListPageAdminComponent;<file_sep>/src/pages/Home/HomePage.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { routes } from '../../routes';
import ProductPageContainer from '../Product/ProductPageContainer';
import ProdutcListPageComponent from './ProductListPage/ProductListPageContainer';
import NotFoundPage from '../NotFound/NotFoundPage';

const HomePage = ({
    match,
}) => (
    <div>
        <Switch>
            <Route
                exact
                path={match.path}
                component={ProdutcListPageComponent}
            />
            <Route
                path={routes.product}
                component={ProductPageContainer}
            />
            <Route component={NotFoundPage} />
        </Switch>
    </div>
);

HomePage.propTypes = {
};

export default HomePage;<file_sep>/src/pages/Contact/ContactPage.js
import React from 'react';

const ContactPage = () => (
    <h1>
        Contact page
    </h1>
);

export default ContactPage;<file_sep>/src/modules/admin/adminReducerHandlers/adminReducerProducts.js
import * as constants from '../adminConstants';

export const adminReducerProducts = {
    // Fetch all products
    [constants.FETCH_PRODUCTS_START]: state => ({
        ...state,
        isLoading: true,
        error: null,
    }),
    [constants.FETCH_PRODUCTS_OK]: (state, action) => ({
        ...state,
        isLoading: false,
        products: action.payload.ids,
    }),
    [constants.FETCH_PRODUCTS_ERROR]: (state, action) => ({
        ...state,
        isLoading: false,
        error: action.payload,
    }),

    // Create product
    [constants.CREATE_PRODUCT_START]: state => ({
        ...state,
        error: null,
    }),
    [constants.CREATE_PRODUCT_OK]: (state, action) => ({
        ...state,
        products: action.payload.ids.concat(state.products),
    }),
    [constants.CREATE_PRODUCT_ERROR]: (state, action) => ({
        ...state,
        error: action.payload,
    }),

    // Update product
    [constants.UPDATE_PRODUCT_START]: state => ({
        ...state,
        error: null,
    }),
    [constants.UPDATE_PRODUCT_OK]: (state, action) => ({
        ...state,
        products: state.products.map((product) => {
            if (product === action.payload.ids[0]) {
                return action.payload.ids[0];
            }
            return product;
        }),
    }),
    [constants.UPDATE_PRODUCT_ERROR]: (state, action) => ({
        ...state,
        error: action.payload,
    }),

    // Remove product
    [constants.REMOVE_PRODUCT_START]: state => ({
        ...state,
        error: null,
    }),
    [constants.REMOVE_PRODUCT_OK]: (state, action) => ({
        ...state,
        products: state.products.filter(product => product !== action.payload.ids[0]),
    }),
    [constants.REMOVE_PRODUCT_ERROR]: (state, action) => ({
        ...state,
        error: action.payload,
    }),

    // Get one product
    [constants.GET_PRODUCT_START]: state => ({
        ...state,
        isLoading: true,
        error: null,
    }),
    [constants.GET_PRODUCT_OK]: (state, action) => ({
        ...state,
        isLoading: false,
        products: [action.payload.id[0]].concat(state.products),
    }),
    [constants.GET_PRODUCT_ERROR]: (state, action) => ({
        ...state,
        isLoading: false,
        error: action.payload,
    }),
};<file_sep>/src/api/Api.js
import axios from 'axios';

// ---------------------------------------------
// Provides API for work with products, users,
// auth and token
// ---------------------------------------------

// Provides API for work with token and localStorage

let _token = null;

export const setToken = (token) => {
    _token = token;

    localStorage.setItem('token', token); // eslint-disable-line

    if (_token === 'undefined') {
        _token = null;
    }

    axios.defaults.headers.Authorization = _token
        ? `Bearer ${_token}`
        : null;
};

export const removeToken = () => {
    localStorage.removeItem('token'); // eslint-disable-line

    _token = null;

    axios.defaults.headers.Authorization = null;
};

export const isAuthenticated = () => {
    if (_token === 'undefined') {
        return false;
    }

    return !!_token;
};

export const initApi = () => {
    const token = localStorage.getItem('token'); // eslint-disable-line

    setToken(token);
};

// Provides API for work with proucts on admin side
export const AdminProducts = {
    fetchProducts(offset, limit) {
        const query = `?offset=${offset}&limit=${limit}`;

        return axios.get(`/api/v3/products${query}`);
    },

    getProductsById(id) {
        return axios.get(`/api/v3/products/${id}`);
    },

    createProduct(body) {
        return axios.post('/api/v3/products', body);
    },

    updateProductById(id, body) {
        return axios.patch(`/api/v3/products/${id}`, body);
    },

    removeProductById(id) {
        return axios.delete(`/api/v3/products/${id}`);
    },
};

// Provides API for work with users on admin side
export const AdminUsers = {
    fetchUsers(offset, limit) {
        const query = `?offset=${offset}&limit=${limit}`;

        return axios.get(`/api/v3/users${query}`);
    },

    getUserById(id) {
        return axios.get(`/api/v3/users/${id}`);
    },

    updateUserById(id, body) {
        return axios.patch(`/api/v3/users/${id}`, body);
    },

    removeUserById(id) {
        return axios.delete(`/api/v3/users/${id}`);
    },
};

// Provides API for work with proucts on users side
export const Products = {
    fetchProducts(offset, limit) {
        const query = `?offset=${offset}&limit=${limit}`;

        return axios.get(`/api/v3/products${query}`);
    },

    getProductsById(id) {
        return axios.get(`/api/v3/products/${id}`);
    },
};

// Provides API for work with proucts on cart page
export const Cart = {
    getProductsByIds(ids) {
        const query = 'ids[]=' + ids.join('&ids[]='); // eslint-disable-line
        return axios.get(`/api/v3/products?${query}`);
    },
};

// Provides API for Authenticate
export const Auth = {
    login(body) {
        return axios.post('/api/v3/auth/login', body);
    },

    register(body) {
        return axios.post('/api/v3/auth/register', body);
    },

    remember(body) {
        return axios.post('/api/v3/auth/remember', body);
    },
};

// Provides API for work wtih users on user side
export const User = {
    getCurrent() {
        return axios.get('/api/v3/users/current');
    },
};<file_sep>/src/pages/Home/ProductListPage/ProdutcListPageComponent.js
import React from 'react';
import PaginatePanel from '../../../components/PaginatePanel/PaginatePanel';
import ProdutcListContainer from '../ProductList/ProductListContainer';

const ProdutcListPageComponent = ({
    handleOnPageChange,
    selectedPage,
}) => (
    <div>
        <ProdutcListContainer />
        <PaginatePanel
            onPageChange={handleOnPageChange}
            initialPage={selectedPage}
            pageCount={10}
            pageRangeDisplayed={5}
            marginRangeDisplayed={3}
        />
    </div>
);

export default ProdutcListPageComponent;<file_sep>/src/components/ActionButton/ActionButton.js
import React from 'react';
import classNames from 'classnames/bind';
import s from './ActionButton.module.css';

const cx = classNames.bind(s);

const ActionButton = ({
    smallSize,
    mediumSize,
    children,
    ...props
}) => (
    <button
        className={cx({
            ActionButton,
            ActionButtonLarge: (!smallSize && !mediumSize),
            ActionButtonMedium: mediumSize,
            ActionButtonSmall: smallSize,
        })}
        {...props}
    >
        {children}
    </button>
);

export default ActionButton;<file_sep>/src/pages/Auth/AuthPage.js
import React from 'react';
import {
    NavLink, Link, Route, Switch,
} from 'react-router-dom';
import { routes } from '../../routes';
import s from './AuthPage.module.css';
import LoginPage from './Login/LoginPage';
import RegisterPage from './Register/RegisterPage';
import RememberPage from './Remember/RememberPage';

const AuthPage = () => (
    <div className={s.AuthPage}>
        <div className={s.Logo}>
            <Link to={routes.home}>Internet Store</Link>
        </div>
        <div className={s.AuthForm}>
            <ul className={s.AuthFormNav}>
                <li className={s.AuthFormNavItem}>
                    <NavLink
                        to={routes.authLogin}
                        className={s.AuthFormLink}
                        activeClassName={s.AuthFormActiveLink}
                    >
                        Login
                    </NavLink>
                </li>
                <li className={s.AuthFormNavItem}>
                    <NavLink
                        to={routes.authRegister}
                        className={s.AuthFormLink}
                        activeClassName={s.AuthFormActiveLink}
                    >
                        Register
                    </NavLink>
                </li>
            </ul>
            <Switch>
                <Route path={routes.authLogin} component={LoginPage} />
                <Route path={routes.authRegister} component={RegisterPage} />
                <Route path={routes.authRemember} component={RememberPage} />
            </Switch>
            <div>
                <Link to={routes.authRemember}>Forgot password?</Link>
            </div>
        </div>
    </div>
);

export default AuthPage;<file_sep>/src/pages/Admin/AdminPage.js
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { routes } from '../../routes';
import AdminNav from './AdminNav/AdminNav';
import ProductListAdminContainer from './ProductListPageAdmin/ProductListPageAdminContainer';
import ProductPageAdminContainer from '../ProductAdmin/ProductPageAdminContainer';
import UserListAdminContainer from './UserListPageAdmin/UserListPageAdminContainer';
import UsersPageAdminContainer from '../UsersAdmin/UsersPageAdminContainer';
import NotFoundPage from '../NotFound/NotFoundPage';

const AdminPage = ({
    match,
}) => (
    <div>
        <AdminNav />
        <Switch>
            <Route
                exact
                path={match.path}
                component={ProductListAdminContainer}
            />
            <Route
                path={routes.adminProduct}
                component={ProductPageAdminContainer}
            />
            <Route
                path={routes.adminUsers}
                component={UserListAdminContainer}
            />
            <Route
                path={routes.adminUser}
                component={UsersPageAdminContainer}
            />
            <Route component={NotFoundPage} />
        </Switch>
    </div>
);
export default AdminPage;<file_sep>/src/components/SearchForm/SearchForm.js
import React from 'react';
import { Form, Field } from 'react-final-form';
/* import InputForm from '../InputForm/InputForm';
import ActionButton from '../ActionButton/ActionButton'; */
import s from './SearchForm.module.css';

const validate = (values) => {
    const errors = {};

    if (!values.search || values.search.trim().length === 0) {
        errors.search = 'Field is empty';
    }

    return errors;
};

const SearchForm = () => {
    const onSubmit = (values) => { // eslint-disable-line

    };

    return (
        <div className={s.SearchContainer}>
            <Form
                onSubmit={onSubmit}
                validate={validate}
                render={({ handleSubmit }) => ( // eslint-disable-line
                    <>
                        <Field name="search">
                            {({ input }) => (
                                <input
                                    className={s.SearchField}
                                    {...input}
                                    type="search"
                                    placeholder="What are you looking for..."
                                />
                            )}
                        </Field>
                        <button
                            className={s.SearchButton}
                        >
                            Search
                        </button>
                    </>

                )}
            />
        </div>
    );
};

export default SearchForm;<file_sep>/src/pages/Home/ProductListPage/ProductListPageContainer.js

import { connect } from 'react-redux';
import {
    compose, lifecycle, branch, renderComponent, mapProps, withHandlers,
} from 'recompose';
import * as productsOperations from '../../../modules/products/productsOperations';
import * as productsActions from '../../../modules/products/productsActions';
import ProdutcListPageComponent from './ProdutcListPageComponent';
import ErrorLoadign from '../../../components/ErrorLoading/ErrorLoading';

const mapStateToProps = state => ({
    isError: !!state.products.error,
    error: state.products.error,
    selectedPage: state.products.page,
    offset: state.products.offset,
});

const mapStateToDispatch = {
    fetchProducts: productsOperations.fetchProducts,
    setPage: productsActions.setPage,
};

export default compose(
    connect(
        mapStateToProps,
        mapStateToDispatch,
    ),
    branch(
        props => props.isError,
        renderComponent(ErrorLoadign),
    ),
    withHandlers({
        handleOnPageChange: props => ({ selected }) => {
            props.setPage({ page: selected });
        },
    }),
    lifecycle({
        componentDidUpdate(nextProps) {
            if (this.props.offset !== nextProps.offset) {
                this.props.fetchProducts(true);
            }
        },
        componentDidMount() {
            this.props.fetchProducts();
        },
    }),
    mapProps(props => ({
        handleOnPageChange: props.handleOnPageChange,
        selectedPage: props.selectedPage,
    })),
)(ProdutcListPageComponent);<file_sep>/src/modules/admin/adminSelectors.js
import { createSelector } from 'reselect';

const getProductsIds = state => state.admin.products;
const getProductById = (state, id) => state.entities.products[id];
const getProductsEntities = state => state.entities.products;

export const getProducst = createSelector(
    [getProductsIds, getProductsEntities],
    (products, entities) => products.map(id => entities[id]),
);

export const getProduct = createSelector(getProductById, result => result);

const getUsersIds = state => state.admin.users;
const getUserById = (state, id) => state.entities.users[id];
const getUsersEntities = state => state.entities.users;

export const getUsers = createSelector(
    [getUsersIds, getUsersEntities],
    (products, entities) => products.map(id => entities[id]),
);

export const getUser = createSelector(getUserById, result => result);<file_sep>/src/components/UserForm/UserForm.js
import React from 'react';
import { Form, Field } from 'react-final-form';
import { FORM_ERROR } from 'final-form';
import InputForm from '../InputForm/InputForm';
import ActionButton from '../ActionButton/ActionButton';
import ErrorSubmiting from '../ErrorSubmiting/ErrorSubmiting';
import s from './UserForm.module.css';

const validate = (values) => {
    const errors = {};

    if (!values.firstName || values.firstName.trim().length === 0) {
        errors.firstName = 'Please enter a valid first name';
    }

    if (!values.lastName || values.lastName.trim().length === 0) {
        errors.lastName = 'Please enter a valid last name';
    }

    if (!values.email || values.email.trim().length === 0 || !values.email.includes('@')) {
        errors.email = 'Please enter a valid email address';
    }

    return errors;
};

const UserForm = ({
    user,
    onSubmitAction,
    onSubmitButtonTitle,
    actionAfterSucceeded,
}) => {
    const onSubmit = async (values) => { // eslint-disable-line
        try {
            await onSubmitAction(values);

            if (actionAfterSucceeded) {
                actionAfterSucceeded();
            }
        } catch (err) {
            return {
                [FORM_ERROR]: 'Something went wrong',
            };
        }
    };

    return (
        <>
            <Form
                initialValues={{ ...user }}
                onSubmit={onSubmit}
                validate={validate}
                render={({
                    handleSubmit, submitError, submitting, submitSucceeded,
                }) => (
                    <>
                        <Field name="firstName">
                            {({ input, meta }) => (
                                <InputForm {...input} type="text" placeholder="<NAME>" meta={meta} />
                            )}
                        </Field>
                        <Field name="lastName">
                            {({ input, meta }) => (
                                <InputForm {...input} type="text" placeholder="<NAME>" meta={meta} />
                            )}
                        </Field>
                        <Field name="email">
                            {({ input, meta }) => (
                                <InputForm {...input} type="text" placeholder="Email" meta={meta} />
                            )}
                        </Field>
                        <div className={s.userRoleSwitch}>
                            <div className={s.roleSwitch}>
                                <Field name="role" type="radio" component="input" value="user" />
                                {' '}User{'   '}
                            </div>
                            <div className={s.roleSwitch}>
                                <Field name="role" type="radio" component="input" value="admin" />
                                {' '}Admin{' '}
                            </div>
                        </div>
                        <div>
                            <ActionButton
                                onClick={handleSubmit}
                            >
                                {onSubmitButtonTitle}
                            </ActionButton>
                        </div>
                        {submitting && <h2>Submiting...</h2>}
                        {submitSucceeded && <h2>Complete</h2>}
                        {submitError && <ErrorSubmiting>{submitError}</ErrorSubmiting>}
                    </>
                )}
            />
        </>
    );
};

export default UserForm;<file_sep>/src/pages/TermsAndConditions/TermsAndConditionsPage.js
import React from 'react';

const TermsAndConditionsPage = () => (
    <h1>
        Terms and Conditions page
    </h1>
);

export default TermsAndConditionsPage;<file_sep>/src/components/ProductLink/ProductLink.js
import React from 'react';
import { Link } from 'react-router-dom';
import { formatRoute } from 'react-router-named-routes';
import classNames from 'classnames/bind';
import s from './ProductLink.module.css';
import { routes } from '../../routes';
import ActionButton from '../ActionButton/ActionButton';
import Counter from '../Counter/Counter';

const cx = classNames.bind(s);

const ProductLink = ({
    title,
    id,
    image,
    price,
    onActionButtonClick,
    actionButtonTitle,
    cart,
    count,
    increment,
    decrement,
    onEnterValueCounter,
}) => (
    <div className={cx({ ProductLink: true, ProductLinkCart: cart })}>
        <div className={cx({ LeftHandSide: cart })}>
            {cart && (
                <Counter
                    value={count}
                    increment={increment}
                    decrement={decrement}
                    onEnterValue={onEnterValueCounter}
                    id={id}
                />
            )
            }
            <Link
                to={formatRoute(routes.product, { id })}
                className={s.linkAnchor}
            >
                <div className={cx({ ProductVisibleCart: cart })}>
                    <div>
                        <img src={image} alt={title} className={s.productImage} title={title} />
                    </div>
                    <div>
                        <h3 className={s.productTitle}>
                            {title}
                        </h3>
                        <div className={s.productPrice}>
                            {price} грн
                        </div>
                    </div>
                </div>
            </Link>
        </div>
        <ActionButton
            onClick={() => onActionButtonClick({ id, value: 1 })}
        >
            {actionButtonTitle}
        </ActionButton>
    </div>
);

export default ProductLink;<file_sep>/src/pages/Admin/UserListAdmin/UserListAdminComponent.js
import React from 'react';
import ProductLinkAdmin from '../../../components/ProductLinkAdmin/ProductLinkAdmin';
import s from './UserListAdmin.module.css';

const UserListAdminComponent = ({
    users,
    openModalForm,
    openModalAsk,
    ModalForm,
    ModalAsk,
}) => (
    <div className={s.ProductListAdmin}>
        {ModalForm()}
        {ModalAsk()}
        {users.map(({ id, firstName, lastName }) => (
            <ProductLinkAdmin
                users
                key={id}
                id={id}
                title={`${firstName} ${lastName}`}
                showModalToUpdateProduct={openModalForm}
                showModalToRemoveProduct={openModalAsk}
            />
        ))}
    </div>
);

export default UserListAdminComponent;<file_sep>/src/pages/User/UserPageContainer.js
import { connect } from 'react-redux';
import { compose, mapProps } from 'recompose';
import ProductPageComponent from './UserPageComponent';

const mapStateToProps = state => ({
    user: state.app.user,
});


export default compose(
    connect(
        mapStateToProps,
    ),
    mapProps(props => ({
        ...props.user,
    })),
)(ProductPageComponent);<file_sep>/src/modules/admin/adminReducerHandlers/adminReducerUsers.js
import * as constants from '../adminConstants';

export const adminReducerUsers = {

    // Fetch all users
    [constants.FETCH_USERS_START]: state => ({
        ...state,
        isLoading: true,
        error: null,
    }),
    [constants.FETCH_USERS_OK]: (state, action) => ({
        ...state,
        isLoading: false,
        users: action.payload.ids,
    }),
    [constants.FETCH_USERS_ERROR]: (state, action) => ({
        ...state,
        isLoading: false,
        error: action.payload,
    }),

    // Update user
    [constants.UPDATE_USER_START]: state => ({
        ...state,
        error: null,
    }),
    [constants.UPDATE_USER_OK]: (state, action) => ({
        ...state,
        users: state.users.map((user) => {
            if (user === action.payload.ids[0]) {
                return action.payload.ids[0];
            }
            return user;
        }),
    }),
    [constants.UPDATE_USER_ERROR]: (state, action) => ({
        ...state,
        error: action.payload,
    }),

    // Remove user
    [constants.REMOVE_USER_START]: state => ({
        ...state,
        error: null,
    }),
    [constants.REMOVE_USER_OK]: (state, action) => ({
        ...state,
        users: state.users.filter(user => user !== action.payload.ids[0]),
    }),
    [constants.REMOVE_USER_ERROR]: (state, action) => ({
        ...state,
        error: action.payload,
    }),

    // Get one user
    [constants.GET_USER_START]: state => ({
        ...state,
        isLoading: true,
        error: null,
    }),
    [constants.GET_USER_OK]: (state, action) => ({
        ...state,
        isLoading: false,
        users: [action.payload.id[0]].concat(state.users),
    }),
    [constants.GET_USER_ERROR]: (state, action) => ({
        ...state,
        isLoading: false,
        error: action.payload,
    }),
};<file_sep>/src/pages/UsersAdmin/UsersPageAdminContainer.js
import { connect } from 'react-redux';
import {
    compose, lifecycle, branch, renderComponent, mapProps,
} from 'recompose';
import UsersPageAdminComponent from './UsersPageAdminComponent';
import * as adminOperations from '../../modules/admin/adminOperations';
import * as adminSelectors from '../../modules/admin/adminSelectors';
import Loader from '../../components/Loader/Loader';
import ErrorLoadign from '../../components/ErrorLoading/ErrorLoading';

const mapStateToProps = (state, props) => ({
    user: adminSelectors.getUser(state, props.match.params.id),
    isLoading: state.admin.isLoading,
    isError: !!state.admin.error,
    error: state.admin.error,
});

const mapStateToDispatch = {
    getUser: adminOperations.getUser,
    removeUser: adminOperations.removeUser,
    updateUser: adminOperations.updateUser,
};

export default compose(
    connect(
        mapStateToProps,
        mapStateToDispatch,
    ),
    lifecycle({
        componentDidMount() {
            this.props.getUser(this.props.match.params.id);
        },
    }),
    branch(
        props => props.isLoading,
        renderComponent(Loader),
        branch(
            props => props.isError,
            renderComponent(ErrorLoadign),
        ),
    ),
    mapProps(props => ({
        updateUser: props.updateUser,
        removeUser: props.removeUser,
        user: props.user,
        history: props.history,
    })),
)(UsersPageAdminComponent);<file_sep>/src/components/Header/Header.js
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { routes } from '../../routes';
import s from './Header.module.css';
import * as appOperations from '../../modules/app/appOperations';
import * as cartSelectors from '../../modules/cart/cartSelectors';
import ActionButton from '../ActionButton/ActionButton';
import SearchForm from '../SearchForm/SearchForm';

const Header = ({
    cartItemsCount,
    user,
    logout,
}) => (
    <div className={s.Header}>
        <div className={s.HeaderTop}>
            <ul className={s.HeaderLinks}>
                <li className={s.HeaderLinkItem}>
                    <Link to={routes.about}>About Us</Link>
                </li>
                <li className={s.HeaderLinkItem}>
                    <Link to={routes.contact}>Contact</Link>
                </li>
                <li className={s.HeaderLinkItem}>
                    <Link to={routes.privacypolicy}>Privacy Policy</Link>
                </li>
                <li className={s.HeaderLinkItem}>
                    <Link to={routes.termsandconditions}>Terms and Conditions</Link>
                </li>
                {user
                && user.id !== undefined
                && (
                    <li className={s.HeaderLinkItem}>
                        <Link to={routes.user}>
                            User: {`${user.firstName} ${user.lastName}`}
                        </Link>
                    </li>
                )}
                <li className={s.HeaderLinkItem}>
                    {user && user.id !== undefined
                        ? (
                            <ActionButton
                                onClick={logout}
                                smallSize
                            >
                                Logout
                            </ActionButton>
                        )
                        : (
                            <Link to={routes.authLogin}>
                                <ActionButton smallSize>Login/Register</ActionButton>
                            </Link>
                        )
                    }
                </li>
            </ul>
        </div>
        <div className={s.HeaderMain}>
            <div className={s.Logo}>
                <Link to={routes.home}>Internet Store</Link>
            </div>
            <div className={s.SearchBox}>
                <SearchForm />
            </div>
            <div>
                <Link to={{ pathname: routes.cart, state: { modal: true } }}>
                    Cart ({cartItemsCount})
                </Link>
            </div>
        </div>
    </div>
);

const mapStateToProps = state => ({
    user: state.app.user,
    cartItemsCount: cartSelectors.getCountItems(state),
});

const mapStateToDispatch = {
    logout: appOperations.logout,
};

export default connect(mapStateToProps, mapStateToDispatch)(Header);<file_sep>/src/layouts/Desktop.js
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import {
    compose, lifecycle, mapProps, withProps,
} from 'recompose';
import s from './Desktop.module.css';
import { routes } from '../routes';
import AdminPage from '../pages/Admin/AdminPage';
import HomePage from '../pages/Home/HomePage';
import CartPage from '../pages/Cart/CartPageContainer';
import CartPageModal from '../pages/Cart/CartModal/CartModal';
import AboutPage from '../pages/About/AboutPage';
import ContactPage from '../pages/Contact/ContactPage';
import PrivacyPolicyPage from '../pages/PrivacyPolicy/PrivacyPolicyPage';
import TermsAndConditionsPage from '../pages/TermsAndConditions/TermsAndConditionsPage';
import NotFoundPage from '../pages/NotFound/NotFoundPage';
import Header from '../components/Header/Header';
import Footer from '../components/Footer/Footer';
import ProtectedRoute from '../components/ProtectedRoute/ProtectedRoute';
import ProtectedRouteUser from '../components/ProtectedRouteUser/ProtectedRouteUser';
import UserPage from '../pages/User/UserPageContainer';

const App = ({ location, previousLocation }) => {
    const isModal = !!(
        location.state
    && location.state.modal
    && previousLocation !== location
    );

    return (
        <div className={s.App}>
            <Header />
            <div className={s.Content}>
                <Switch location={isModal ? previousLocation : location}>
                    <ProtectedRoute path={routes.admin} component={AdminPage} />
                    <Route path={routes.cart} component={CartPage} />
                    <Route path={routes.about} component={AboutPage} />
                    <Route path={routes.contact} component={ContactPage} />
                    <Route path={routes.privacypolicy} component={PrivacyPolicyPage} />
                    <Route path={routes.termsandconditions} component={TermsAndConditionsPage} />
                    <ProtectedRouteUser path={routes.user} component={UserPage} />
                    <Route path={routes.home} component={HomePage} />
                    <Route component={NotFoundPage} />
                </Switch>
            </div>
            {isModal ? <Route path={routes.cart} component={CartPageModal} /> : null}
            <Footer />
        </div>
    );
};


// This logic allows you to open the cart page in the modal window
// when we click on the cart link and on a separate page when we
// go to the cart page directly by the link or reload the page with
// the open modal box of the cart

let previousLocation;

const getPreviousLocation = () => previousLocation;
const setPreviousLocation = (newPreviousLocation) => {
    previousLocation = newPreviousLocation;
};

export default compose(
    withProps(
        (props) => {
            setPreviousLocation(props.location);
        },
    ),
    lifecycle({
        componentWillUpdate(nextProps) {
            const { location } = this.props;

            // Set previousLocation if props.location is not modal
            if (
                nextProps.history.action !== 'POP'
        && (!location.state || !location.state.modal)
            ) {
                setPreviousLocation(this.props.location);
            }
        },
    }),
    mapProps(props => ({
        ...props,
        previousLocation: getPreviousLocation(),
    })),
)(App);<file_sep>/src/modules/cart/cartReducer.js
import { handleActions } from 'redux-actions';
import * as constants from './cartConstants';

const initialState = {
    items: {},
    isLoading: false,
    error: null,
};

export default handleActions(
    {
        [constants.ADD]: (state, action) => ({
            ...state,
            items: addItem(state, action),
        }),
        [constants.REMOVE]: (state, action) => ({
            ...state,
            items: removeItem(state, action),
        }),
        [constants.FETCH_PRODUCTS_START]: state => ({
            ...state,
            isLoading: true,
            error: null,
        }),
        [constants.FETCH_PRODUCTS_OK]: state => ({
            ...state,
            isLoading: false,
        }),
        [constants.FETCH_PRODUCTS_ERROR]: (state, action) => ({
            ...state,
            isLoading: false,
            error: action.payload,
        }),
        [constants.INCREASE]: (state, action) => ({
            ...state,
            items: {
                ...state.items,
                [action.payload.id]: {
                    ...state.items[action.payload.id],
                    count: state.items[action.payload.id].count + 1,
                },
            },
        }),
        [constants.DECREASE]: (state, action) => ({
            ...state,
            items: decrease(state, action),
        }),
        [constants.ENTER_VALUE]: (state, action) => ({
            ...state,
            items: enterValue(state, action),
        }),
    },
    initialState,
);

function addItem(state, action) {
    if (!state.items[action.payload.id]) {
        return {
            ...state.items,
            [action.payload.id]: {
                id: action.payload.id,
                count: action.payload.value,
            },
        };
    }
    return {
        ...state.items,
        [action.payload.id]: {
            id: action.payload.id,
            count: state.items[action.payload.id].count + action.payload.value,
        },
    };
}

function removeItem(state, action) {
    const items = state.items;
    delete items[action.payload.id];
    return {
        ...items,
    };
}

function decrease(state, action) {
    if (state.items[action.payload.id].count - 1 < 0) {
        return state.items;
    }

    return {
        ...state.items,
        [action.payload.id]: {
            ...state.items[action.payload.id],
            count: state.items[action.payload.id].count - 1,
        },
    };
}

function enterValue(state, action) {
    if (action.payload.value < 0) {
        return {
            ...state.items,
            [action.payload.id]: {
                ...state.items[action.payload.id],
                count: 0,
            },
        };
    }

    return {
        ...state.items,
        [action.payload.id]: {
            ...state.items[action.payload.id],
            count: action.payload.value,
        },
    };
}<file_sep>/src/components/Footer/Footer.js
import React from 'react';
import { Link } from 'react-router-dom';
import s from './Footer.module.css';
import { routes } from '../../routes';


const Footer = () => (
    <div className={s.Footer}>
        <div className={s.FooterMain}>
            <ul className={s.FooterLinks}>
                <h4>Store information</h4>
                <li className={s.FooterLinkItem}>
                    <Link to={routes.about}>About Us</Link>
                </li>
                <li className={s.FooterLinkItem}>
                    <Link to={routes.contact}>Contact</Link>
                </li>
                <li className={s.FooterLinkItem}>
                    <Link to={routes.privacypolicy}>Privacy Policy</Link>
                </li>
                <li className={s.FooterLinkItem}>
                    <Link to={routes.termsandconditions}>Terms and Conditions</Link>
                </li>
            </ul>
        </div>
        <h3 className={s.FooterS}>Internet Store 2018</h3>
    </div>
);

export default Footer;<file_sep>/src/routes.js
export const routes = {
    admin: '/admin',
    adminProduct: '/admin/products/:id',
    adminUser: '/admin/user/:id',
    adminUsers: '/admin/users',
    home: '/',
    product: '/products/:id',
    user: '/user',
    cart: '/cart',
    cartCheckout: '/cart/checkout',
    about: '/about',
    contact: '/contact',
    termsandconditions: '/termsandconditions',
    privacypolicy: '/privacypolicy',
    auth: '/auth',
    authRemember: '/auth/remember',
    authLogin: '/auth/login',
    authRegister: '/auth/register',
}<file_sep>/src/modules/products/productsConstants.js
export const FETCH_PRODUCTS_START = 'products/FETCH_PRODUCTS_START';
export const FETCH_PRODUCTS_OK = 'products/FETCH_PRODUCTS_OK';
export const FETCH_PRODUCTS_ERROR = 'products/FETCH_PRODUCTS_ERROR';

export const GET_PRODUCT_START = 'products/GET_PRODUCT_START';
export const GET_PRODUCT_OK = 'products/GET_PRODUCT_OK';
export const GET_PRODUCT_ERROR = 'products/GET_PRODUCT_ERROR';

export const SET_PAGE = 'products/SET_PAGE';<file_sep>/src/modules/entities/entitiesReducer.js
const initialState = {
    products: {
        // [product.id]: {
        //     ...product
        // }
    },
    users: {
        // [user.id]: {
        //     ...user
        // }
    },
};

export default function entitiesReducer(state = initialState, action) {
    if (action.payload && action.payload.entities) {
        const newState = { ...state };
        Object.keys(action.payload.entities).forEach((key) => {
            Object.assign(newState[key], action.payload.entities[key]);
        });

        return newState;
    }

    return state;
}<file_sep>/src/schemes/products.js
import T from 'prop-types';

export const productPropTypes = T.shape({
    id: T.string.isRequired,
    title: T.string.isRequired,
    description: T.string.isRequired,
    image: T.string.isRequired,
    price: T.number.isRequired,
}).isRequired;<file_sep>/src/pages/Cart/CartList/CartList.js
import React from 'react';
import s from './CartList.module.css';

const CartList = ({
    products,
    itemsCart,
    renderProductLink,
}) => {
    if (products.length === 0) {
        return (
            <h2 className={s.EmptyCart}>Your cart is empty</h2>
        );
    }

    return (
        <div className={s.CartList}>
            {products.map(item => renderProductLink(item, itemsCart))}
        </div>
    );
};

export default CartList;<file_sep>/src/modules/rootModule.js
import { combineReducers } from 'redux';
import products from './products/productsReducer';
import cart from './cart/cartReducer';
import entities from './entities/entitiesReducer';
import admin from './admin/adminReducer';
import app from './app/appReducer';

export default combineReducers({
    products,
    cart,
    entities,
    admin,
    app,
});<file_sep>/src/api/schemes.js
import { schema } from 'normalizr';

// Provides schemes to normalizate data

export const Product = new schema.Entity('products');
export const ProductCollection = [Product];

export const AdminProduct = new schema.Entity('products');
export const AdminProductCollection = [Product];

export const AdminUser = new schema.Entity('users');
export const AdminUserCollection = [AdminUser];<file_sep>/src/modules/products/productsOperations.js
import { normalize } from 'normalizr';
import * as schemes from '../../api/schemes';
import * as actions from './productsActions';
import * as Api from '../../api/Api';

export const fetchProducts = refresh => async (dispatch, getState) => {
    try {
        const limit = getState().products.limit;
        const offset = getState().products.offset;

        const ids = getState().products.products;

        // When we go straight through the link to the product page,
        // we can already have one product
        if (ids.length > 1 && !refresh) {
            return;
        }

        dispatch(actions.fetchProductsStart());

        const res = await Api.Products.fetchProducts(offset, limit);
        const { result, entities } = normalize(res.data, schemes.ProductCollection);

        dispatch(actions.fetchProductsOk({
            ids: result,
            entities,
        }));
    } catch (err) {
        dispatch(actions.fetchProductsError(err.message));
    }
};

export const getProduct = id => async (dispatch, getState) => {
    try {
        const product = getState().entities.products[id];

        if (product) {
            return;
        }

        dispatch(actions.getProductStart());

        const res = await Api.Products.getProductsById(id);
        const { result, entities } = normalize(res.data, schemes.ProductCollection);

        dispatch(actions.getProductOk({
            id: result,
            entities,
        }));
    } catch (err) {
        dispatch(actions.getProductError(err.message));
    }
};<file_sep>/src/pages/Admin/AdminNav/AdminNav.js
import React from 'react';
import { NavLink } from 'react-router-dom';
import s from './AdminNav.module.css';
import { routes } from '../../../routes';

const AdminNav = () => (
    <div className={s.AdminNav}>
        <NavLink
            exact
            to={routes.admin}
            className={s.AdminNavLink}
            activeClassName={s.AdminNavLinkActive}
        >
            Products
        </NavLink>
        <NavLink
            to={routes.adminUsers}
            className={s.AdminNavLink}
            activeClassName={s.AdminNavLinkActive}
        >
            Users
        </NavLink>
    </div>
);

export default AdminNav;<file_sep>/src/pages/UsersAdmin/UsersPageAdminComponent.js
import React from 'react';
import { Form } from 'react-final-form';
import { FORM_ERROR } from 'final-form';
import ActionButton from '../../components/ActionButton/ActionButton';
import UserForm from '../../components/UserForm/UserForm';
import ErrorSubmiting from '../../components/ErrorSubmiting/ErrorSubmiting';

const UserPageAdminComponent = ({
    user,
    updateUser,
    removeUser,
    history,
}) => {
    const onSubmitRemove = async ({ id }) => { // eslint-disable-line
        try {
            await removeUser(id);
            history.go(-1);
        } catch (err) {
            return {
                [FORM_ERROR]: 'Somethings went wrogn',
            };
        }
    };

    return (
        <div>
            <UserForm
                user={user}
                onSubmitAction={updateUser}
                onSubmitButtonTitle="Update"
                actionAfterSucceeded={history.goBack}
            />
            <Form
                initialValues={{ ...user }}
                onSubmit={onSubmitRemove}
                render={({
                    handleSubmit, submitError, submitting, submitSucceeded,
                }) => (
                    <>
                        <ActionButton onClick={handleSubmit}>Remove</ActionButton>
                        {submitting && <h2>Submiting...</h2>}
                        {submitSucceeded && <h2>Complete</h2>}
                        {submitError && <ErrorSubmiting>{submitError}</ErrorSubmiting>}
                    </>
                )}
            />
        </div>
    );
};

export default UserPageAdminComponent;