olegnn / react-shopping-cart

Powerful shopping cart
MIT License
130 stars 41 forks source link

React shopping cart

npm Build Status npm peerDependencies Status dependencies devDependencies Status

Shopping cart package provides several components:

which can be used separately or in union. By default Redux is the framework to operate with data.

So, it's your choice to use Redux or not, but its reducers, actions and action types are already included.

Pay attention! All components are Pure.

Meta

Demo

Latest version demo (example1)

Usage

yarn add react-shopping-cart
npm i --save react-shopping-cart

Examples

In all cases you must include bootstrap version 4 (^alpha 0.6) in your project

import "bootstrap/dist/css/bootstrap.css";

And if you want to see animation, also include animate.css

import "animate.css/animate.min.css";

Also want some icons?

import "font-awesome/css/font-awesome.min.css";

With Redux. After store initialization you must dispatch setCartCurrency action or 'USD' will be used as cart's currency.

import React, { PureComponent } from "react";
import { Provider } from "react-redux";
import { createStore, combineReducers } from "redux";
import {
  Cart,
  Product,
  CheckoutButton,
  cartLocalization,
  cartReducer,
  setCartCurrency
} from "react-shopping-cart";

import "bootstrap/dist/css/bootstrap.css";
import "animate.css/animate.min.css";
import "font-awesome/css/font-awesome.min.css";

const { getDefaultLocalization } = cartLocalization;

// You may take localization object from wherever you want, that's just an example
// For more information, see localization section
const iPadCaseLocalization = {
  color: "Color",
  iPadCase: "iPad case",
  red: "Red",
  green: "Green",
  yellow: "Yellow",
  GBP: "£",
  EUR: "€",
  USD: "$"
};

const iPadPropertiesWithAdditionalCostLocalization = {
  yellow: "Yellow (+{cost, number, CUR})"
};

const store = createStore(
  combineReducers({
    cart: cartReducer
    // Your own reducers, sir
  })
);

store.dispatch(setCartCurrency("USD"));

class App extends PureComponent {
  state = {
    product: {
      name: "iPadCase",
      id: "ipad-case",
      path: "/shop/ipad-case/",
      properties: {
        color: [
          "red",
          "green",
          {
            additionalCost: {
              GBP: 1,
              EUR: 2,
              USD: 3.5
            },
            value: "yellow"
          }
        ]
      },
      propertiesToShowInCart: ["color"],
      prices: { GBP: 70, EUR: 80, USD: 90 },
      currency: "GBP",
      imageSrc: "1-483x321.jpeg"
    },
    getProductLocalization: getDefaultLocalization("product", "en", {
      ...iPadCaseLocalization,
      ...iPadPropertiesWithAdditionalCostLocalization
    }),
    getCheckoutButtonLocalization: getDefaultLocalization(
      "checkoutButton",
      "en",
      iPadCaseLocalization
    ),
    getCartLocalization: getDefaultLocalization(
      "cart",
      "en",
      iPadCaseLocalization
    )
  };

  render() {
    const {
      product,
      getCheckoutButtonLocalization,
      getProductLocalization,
      getCartLocalization
    } = this.state;

    const checkoutButtonElement = (
      <CheckoutButton
        getLocalization={getCheckoutButtonLocalization}
        checkoutURL="/to/my/checkout"
      />
    );
    return (
      <Provider store={store}>
        <div className="container">
          <Product
            {...product}
            checkoutButton={checkoutButtonElement}
            getLocalization={getProductLocalization}
          />
          <Cart
            checkoutButton={checkoutButtonElement}
            getLocalization={getCartLocalization}
          />
        </div>
      </Provider>
    );
  }
}

export default App;
// You may also import actions and actionTypes

import { cartActions, cartActionTypes } from "react-shopping-cart";

// And do some cool things with them

Without redux

import React, { PureComponent } from "react";
import {
  CartComponent,
  ProductComponent,
  CheckoutButtonComponent,
  cartLocalization
} from "react-shopping-cart";

import "bootstrap/dist/css/bootstrap.css";
import "animate.css/animate.min.css";
import "font-awesome/css/font-awesome.min.css";

const { getDefaultLocalization } = cartLocalization;

// You may take localization object from wherever you want, that's just an example
// For more information, see localization section
const iPadCaseLocalization = {
  color: "Color",
  iPadCase: "iPad case",
  red: "Red",
  green: "Green",
  yellow: "Yellow",
  GBP: "£",
  EUR: "€",
  USD: "$"
};

const iPadPropertiesWithAdditionalCostLocalization = {
  yellow: "Yellow (+{cost, number, CUR})"
};

class App extends PureComponent {
  state = {
    products: {},
    product: {
      name: "iPadCase",
      id: "ipad-case",
      path: "/shop/ipad-case/",
      properties: {
        color: [
          "red",
          "green",
          {
            additionalCost: {
              GBP: 1,
              EUR: 2,
              USD: 3.5
            },
            value: "yellow"
          }
        ]
      },
      propertiesToShowInCart: ["color"],
      prices: { GBP: 70, EUR: 80, USD: 90 },
      currency: "GBP",
      imageSrc: "1-483x321.jpeg"
    },
    getProductLocalization: getDefaultLocalization("product", "en", {
      ...iPadCaseLocalization,
      ...iPadPropertiesWithAdditionalCostLocalization
    }),
    getCheckoutButtonLocalization: getDefaultLocalization(
      "checkoutButton",
      "en",
      iPadCaseLocalization
    ),
    getCartLocalization: getDefaultLocalization(
      "cart",
      "en",
      iPadCaseLocalization
    )
  };

  addProduct = (key, product, currency) =>
    void this.setState(
      ({
        products: { [key]: cartProduct = { quantity: 0 }, ...restOfProducts }
      }) => ({
        products: {
          ...restOfProducts,
          [key]: {
            ...product,
            quantity: product.quantity + cartProduct.quantity
          }
        }
      })
    );

  generateProductKey = (id, properties) =>
    `${id}/${Object.entries(properties).join("_")}`;

  updateProduct = (key, updatedProduct) => void console.log(":)");

  removeProduct = key => void console.log(":C");

  render() {
    const {
      addProduct,
      generateProductKey,
      updateProduct,
      removeProduct,
      state
    } = this;

    const {
      getProductLocalization,
      getCheckoutButtonLocalization,
      getCartLocalization,
      products,
      product
    } = state;

    const checkoutButtonElement = (
      <CheckoutButtonComponent
        grandTotal={500}
        hidden={false}
        checkoutURL="/to/my/checkout"
        currency="GBP"
        getLocalization={getCheckoutButtonLocalization}
      />
    );
    return (
      <div className="container">
        <ProductComponent
          {...product}
          checkoutButton={checkoutButtonElement}
          onAddProduct={
            addProduct
            // Help product to get into the cart
          }
          generateProductKey={
            generateProductKey
            // create product key as you wish
          }
          getLocalization={getProductLocalization}
        />

        <CartComponent
          products={
            products
            // Provide your own product's Object(Look at Products)
          }
          onUpdateProduct={
            updateProduct
            // Update something
          }
          getLocalization={getCartLocalization}
          currency="GBP"
          onRemoveProduct={
            removeProduct
            // Remove something
          }
          checkoutButton={checkoutButtonElement}
          isCartEmpty={false}
          getLocalization={getCartLocalization}
        />
      </div>
    );
  }
}

export default App;

Localization

The default localization library is intl-messageformat. In order to localize your cart, you can chose one of the possible ways:

Generally, components require a function, which takes id and params(optional) and returns string, based on received arguments.

The first one should look like that if you're also using intl-messageformat:

  import React from 'react';
  import IntlMessageFormat from 'intl-messageformat';
  import { Cart } from 'react-shopping-cart';

  const localization = {
    en: {
      cart : {
        GBP: '£',
      },
    },
  };

  const getLocalization = (localizationPart, language, id, params = {}) =>  
    new IntlMessageFormat(localizationPart[id], language).format(params);

  <Cart
    getLocalization={(...args) => getLocalization(localization.en.cart, 'en', ...args)}
  />

Or you could use getDefaultLocalization function from cartLocalization:

  import React from 'react';
  import { Cart, cartLocalization } from 'react-shopping-cart';

  const { getDefaultLocalization } = cartLocalization;

  const localization = {
    GBP: '£',
    USD: '$',
  };

  <Cart
    getLocalization={getDefaultLocalization('cart', 'en', localization)}
  />

Example usage of getLocalization function from cartLocalization:

  import React from 'react';
  import { Cart, cartLocalization } from 'react-shopping-cart';

  const { getLocalization, defaultLocalization } = cartLocalization;

  const localization = {
    en: {
      cart : {
        GBP: '£',
      },
    },
  };

  const mergedEnCartLocalization = {
    ...localization.en.cart,
    ...defaultLocalization.en.cart,
  };

  <Cart
    getLocalization={(...args) => getLocalization(mergedEnCartLocalization, 'en', ...args)}
  />

For built-in getLocalization function you may write your translation for default statements as a string or object in format { component : Function | string, text : string, props? : object }. Because all components are pure, in order to relocalize your components, you should pass new getLocalization function, not old with changed scope.

Localization default ids and params bindings:

Table of Contents

Cart

Extends React.PureComponent

Component which represents shopping cart.

Meta

Props

Type: Object

Properties

CartProduct

Extends React.PureComponent

React component to display product in cart.

Meta

ProductPropertyLabel

Extends React.PureComponent

React component to display product's property value in cart.

Meta

CheckoutButton

Extends React.PureComponent

Checkout button with grand total.

Meta

Props

Type: Object

Properties

helpers

Meta

configure

Parameters

Returns React$ComponentType<Props>

isNaturalNumber

Parameters

Returns boolean

parseInteger

Parameters

Returns number

isObject

Parameters

Returns boolean

getAbsoluteOffsetTop

Parameters

Returns number

DefaultLinkComponent

Parameters

Returns React$Element<any>

fixInputValueStartingWithZero

Parameters

scrollFunction

Parameters

Product

Extends React.PureComponent

React component - Product form with price.

Meta

Props

Type: Object

Properties

ProductPropertiesOptions

Type: Object<string, PropertyOptions>

ScrollPosition

Type: (number | function (currentTarget: Element): number)

ScrollFunction

Type: function (currentTarget: EventTarget, scrollPosition: (number | function (currentTarget: Element): number), scrollAnimationConfig: Object): void

ProductPropertyInput

Extends React.PureComponent

React form for product property(options select only).

Meta

OptionIndex

Type: Object<string, number>

OptionObject

Type: Object

PropertyOption

Type: (ProductPropertyOption | OptionObject)

PropertyOptions

Type: Array<PropertyOption>

OnChange

Type: function (obj: {value: OptionIndex}): void

Shopping cart's data types

Meta

ProductPropertyOption

Type: (string | number)

ProductProperties

Type: Object<string, (string | number)>

Prices

Type: Object<string, number>

ProductData

Properties

Examples

{
   id: 'macbook-case',
   quantity: 3,
   properties: {
     color: 'red'
   },
   name: 'macbookCase',
   prices: {
    GBP: 50
   },
   path: '/shop/macbook-case/',
   imageSrc: '/shop/macbook-case/1-483x321.jpeg',
   propertiesToShowInCart: ['color']
 }

Products

Type: Object<string, ProductData>

GenerateProductKey

Type: function (id: string, properties: ProductProperties): string

AddProduct

Type: function (key: string, product: ProductData, currency: string): void

UpdateProduct

Type: function (key: string, updatedProduct: ProductData): void

RemoveProduct

Type: function (key: string): void

GetLocalization

Type: function (id: string, params: Object): (string | React$Element<any>)

CartAddAction

Properties

CartUpdateAction

Properties

CartRemoveAction

Properties

CartEmptyAction

Properties

CartSetCurrencyAction

Properties

CartAction

Type: (CartAddAction | CartUpdateAction | CartRemoveAction | CartEmptyAction | CartSetCurrencyAction)

LocalizationPattern

Type: (string | {component: (string | React$ComponentType<any>), props: Object?, text: string})

Localization

Type: Object<string, LocalizationPattern>

MultiLocalization

Type: Object<string, Object<string, Localization>>

CartState

Properties

DefaultLinkComponentProps

Properties

Link$Component

Type: function (DefaultLinkComponentProps): React$Element<any>

Development

Developer mode

Run webpack-dev-server for example1

yarn start
npm run start

Build

yarn build
npm run build

And then check dist folder

Build Example

yarn build_example
npm run build_example

And then check examples folder

Testing

Jest is used for tests

yarn test
npm run test

Linter

ESLint is used as a linter

yarn lint
npm run lint

Formatter

prettier-eslint is used as a formatter

yarn fmt
npm run fmt

Flow Type

Check types in project using Flow

yarn flow
npm run flow

Autodoc

Generate doc using documentation js

yarn doc
npm run doc

And then look at README.md