DEV Community

kris
kris

Posted on • Originally published at reactninja.io on

Building simple Point of Sale system with Node.js & React.js

Building real time applications can be exciting, the idea that pages can be updated without reloading has always been of interest to me.
For the this tutorial we will be creating a real-time point of sale system using node.js and react.js

Get the source code and see demo here

This tutorial will comprise of three parts:

Part 1 (BackEnd)

  1. Frameworks description
  2. Building the Node app from scratch
  3. Testing with Postman

Part 2 (FrontEnd)
1.Creating a Template React app.
2.Creating Routes and Views with Code Description.

I recommend using the Vscode Editor for this tutorial.

Frameworks Description and Installation

Below are the libraries and frameworks we will be using:

nedb: NeDB is much like SQLite in that it is a smaller, embeddable version of a much larger database system.NeDB is a smaller NoSQL datastore that mimics MongoDB.

socket.io:Socket.IO enables real-time bidirectional event-based communication.It works on every platform, browser or device, focusing equally on reliability and speed.
express: Express is a Fast, unopinionated, minimalist web framework for Node.js. express features will enable us to create our web server.
async
nodemon: Nodemon checks for changes in your source and automatically restart your server.
body-parser: body-parser extract the entire body portion of an incoming request stream and exposes it on req.body .
http: Http allows Node.js to transfer data over the Hyper Text Transfer Protocol (HTTP).

Let’s continue by creating the backend with node.js, I will assume you have node and npm installed.

**Building the Node app from scratch**

For this tutorial we are going to create the Node app (express app) from scratch. it can also be done automatically using the ejs template.

Create a directory via your Command Line Interface (CLI) named real-time-pos-system

mkdir real-rime-pos-system

Access the folder via CLI thus:

cd real-time-pos-system

Inside your real-time-pos-system folder create new folder named serverfrom CLI

mkdir server

Let’s install our dependencies:

npm init

Press enter button for the following asked questions:

package name: (server) Press Enter
version: (1.0.0) Press Enter
description: Node.js app that connect the react-pos app to the Database 
entry point:(index.js) Press Enter
test command: Press Enter
git repository: Press Enter
keywords: Press Enter
author: Enter Your Name
license: (ISC) MIT

you will be shown the following message:

{
    "name": "server"
    version: "1.0.0"
    "description": "Node.js app that connect the react-pos app to the Database 
    "main" : "index.js",
    "scripts": {
       test": "echo \"Error: no test specified\ specified\" && exit 1"
},
"author": "Your Name",
"license": "MIT"
}
Is this ok?(yes) yes

Install the following dependencies:

npm install express --save

npm install http --save

npm install nodemon --save

npm install nedb --save

Create a file named index.js in your real-time-pos-system folder using your Editor.

index.js is the entry point for our node app, as you can see it is located in the root of our app.

insert the following code in your index.js file

var express = require("express"),
  http = require("http"),
  port = 80,
  app = require("express")(),
  server = http.createServer(app),
  bodyParser = require("body-parser"),
  io = require("socket.io")(server),
  liveCart;
console.log("Real time POS running");
console.log("Server started");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.all("/*", function(req, res, next) {
  // CORS headers
  res.header("Access-Control-Allow-Origin", "*"); // restrict it to the required domain
  res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");
  // Set custom headers for CORS
  res.header(
    "Access-Control-Allow-Headers",
    "Content-type,Accept,X-Access-Token,X-Key"
  );
  if (req.method == "OPTIONS") {
    res.status(200).end();
  } else {
    next();
  }
});
app.get("/", function(req, res) {
  res.send(" Real time POS web app running.");
});
app.use("/api/inventory", require("./api/inventory"));
app.use("/api", require("./api/transactions"));
// Websocket logic for Live Cart
io.on("connection", function(socket) {
  socket.on("cart-transaction-complete", function() {
    socket.broadcast.emit("update-live-cart-display", {});
  });
 // on page load, show user current cart
  socket.on("live-cart-page-loaded", function() {
    socket.emit("update-live-cart-display", liveCart);
  });
 // when client connected, make client update live cart
  socket.emit("update-live-cart-display", liveCart);
 // when the cart data is updated by the POS
  socket.on("update-live-cart", function(cartData) {
    // keep track of it
    liveCart = cartData;
 // broadcast updated live cart to all websocket clients
    socket.broadcast.emit("update-live-cart-display", liveCart);
  });
});
server.listen(port, () => console.log(`Listening on port ${port}`));

index.js Explained

This file is the entry point to our node express app. it is comprised of routes that will handle requests and responses to and from the browser.

Below are dependencies assigned to variables.

var express = require("express"),
  http = require("http"),
  port = 80,
  app = require("express")(),
  server = http.createServer(app),
  bodyParser = require("body-parser"),
  io = require("socket.io")(server),
  liveCart

Below, The express variable app is used to allows data to be sent to the database using http request body.

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }))

Below are imported files that will represent inventory and transaction routes.

app.use("/api/inventory", require("./api/inventory"))

app.use("/api/transactions", require("./api/transactions"))

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts) on a web page to be requested from another domain outside the domain from which the first resource was served. — Wikipedia

Below, the node app is restricted to resources within using CORS and allows specified methods GET PUT POST DELETE and OPTIONS to be used.

app.all("/*", function(req, res, next) {
  // CORS headers
  res.header("Access-Control-Allow-Origin", "*"); // restrict it to the required domain
  res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");
  // Set custom headers for CORS
  res.header(
    "Access-Control-Allow-Headers",
    "Content-type,Accept,X-Access-Token,X-Key"
  );
  if (req.method == "OPTIONS") {
    res.status(200).end();
  } else {
    next();
  }
});

Below is the Node app default route

app.get("/", function(req, res) {
  res.send(" Real time POS web app running.");
});

The Websocket logic for Live Cart

io.on("connection", function(socket) {
  socket.on("cart-transaction-complete", function() {
    socket.broadcast.emit("update-live-cart-display", {});
  });

On page load, give user current cart

socket.on("live-cart-page-loaded", function() {
    socket.emit("update-live-cart-display", liveCart);
  });

On page load, make client update live cart

socket.emit("update-live-cart-display", liveCart)

When the cart data is updated by the POS and keeps track of it

socket.on("update-live-cart", function(cartData) {
    liveCart = cartData;

Broadcasts updated live cart to all websocket clients

socket.broadcast.emit("update-live-cart-display", liveCart);
 });

Let’s continue, create a directory inside server directory:

mkdir api

Create two files named inventory.js and transactions.js in your api folder

insert the following code to your inventory.js:

var app = require("express")();
var server = require("http").Server(app);
var bodyParser = require("body-parser");
var Datastore = require("nedb");
var async = require("async");
app.use(bodyParser.json());
module.exports = app;
// Creates Database
var inventoryDB = new Datastore({
  filename: "./server/databases/inventory.db",
  autoload: true
});
// GET inventory
app.get("/", function(req, res) {
  res.send("Inventory API");
});
// GET a product from inventory by _id
app.get("/product/:productId", function(req, res) {
  if (!req.params.productId) {
    res.status(500).send("ID field is required.");
  } else {
    inventoryDB.findOne({ _id: req.params.productId }, function(err, product) {
      res.send(product);
    });
  }
});
// GET all inventory products
app.get("/products", function(req, res) {
  inventoryDB.find({}, function(err, docs) {
    console.log("sending inventory products");
    res.send(docs);
  });
});
// Create inventory product
app.post("/product", function(req, res) {
  var newProduct = req.body;
 inventoryDB.insert(newProduct, function(err, product) {
    if (err) res.status(500).send(err);
    else res.send(product);
  });
});
app.delete("/product/:productId", function(req, res) {
  inventoryDB.remove({ _id: req.params.productId }, function(err, numRemoved) {
    if (err) res.status(500).send(err);
    else res.sendStatus(200);
  });
});
// Updates inventory product
app.put("/product", function(req, res) {
  var productId = req.body._id;
 inventoryDB.update({ _id: productId }, req.body, {}, function(
    err,
    numReplaced,
    product
  ) {
    if (err) res.status(500).send(err);
    else res.sendStatus(200);
  });
});
app.decrementInventory = function(products) {
  async.eachSeries(products, function(transactionProduct, callback) {
    inventoryDB.findOne({ _id: transactionProduct._id }, function(
      err,
      product
    ) {
      // catch manually added items (don't exist in inventory)
      if (!product || !product.quantity_on_hand) {
        callback();
      } else {
        var updatedQuantity =
          parseInt(product.quantity_on_hand) -
          parseInt(transactionProduct.quantity);
 inventoryDB.update(
          { _id: product._id },
          { $set: { quantity_on_hand: updatedQuantity } },
          {},
          callback
        );
      }
    });
  });
};

inventory.js Explained
The necessary dependencies are assigned to variables app, server, bodyParser and Datastore. The app.use(bodyParser.json()) will allow the body of a http request to sent to the database.

An inventory variable inventoryDB is assigned with an instance of
nedb variable Datastore we created earlier. The DataStore
instance has two options filename which specifies the path of the database and autoload, which autumatically loads the database if set to true.

The app.get("/, function(req, res) function is the default path for the inventory database.

The app.get("/product/:/productId function enables the app to get a product from the inventory database using it’s ID.

The app.get("/products", function(req, res) function gets all products from the inventory database.

The app.post("/product", function(req, res) function is used to save an inventory product to the database.

The app.delete("/product/:productId", function(req, res) is used to delete product using product ID.

The app.put("/product", function(req, res) updates a product using it product ID.

Let’s continue, insert the following code to your transaction.js file:

var app = require('express')()
var server = require('http').Server(app)
var bodyParser = require('body-parser')
var Datastore = require('nedb')
var Inventory = require('./inventory')
app.use(bodyParser.json())
module.exports = app
// Create Database
var Transactions = new Datastore({ 
    filename: './server/databases/transactions.db', 
    autoload: true 
})
app.get('/', function (req, res) {
    res.send('Transactions API')
})
// GET all transactions
app.get('/all', function (req, res) {
   Transactions.find({}, function (err, docs) {
        res.send(docs)
    })
})
// GET all transactions
app.get('/limit', function (req, res) {
   var limit = parseInt(req.query.limit, 10)
    if (!limit) limit = 5
   Transactions.find({}).limit(limit).sort({ date: -1 }).exec(function (err, docs) {
      res.send(docs)
    })
})
// GET total sales for the current day
app.get('/day-total', function (req, res) {
   // if date is provided
    if (req.query.date) {
        startDate = new Date(req.query.date)
        startDate.setHours(0,0,0,0)
 endDate = new Date(req.query.date)
        endDate.setHours(23,59,59,999)
    }
    else {
 // beginning of current day
        var startDate = new Date()
        startDate.setHours(0,0,0,0)
 // end of current day
        var endDate = new Date()
        endDate.setHours(23,59,59,999)  
    }

   Transactions.find({ date: { $gte: startDate.toJSON(), $lte: endDate.toJSON() } }, function (err, docs) {
        
        var result = {
            date: startDate
        }
 if (docs) {
 var total = docs.reduce(function (p, c) {
                return p + c.total
            }, 0.00)
 result.total = parseFloat(parseFloat(total).toFixed(2))
 res.send(result)
        }
        else {
            result.total = 0
            res.send(result)
        }
    })  
})
// GET transactions for a particular date
app.get('/by-date', function (req, res) {
    
    var startDate = new Date(2018, 2, 21)
    startDate.setHours(0,0,0,0)
   var endDate = new Date(2015, 2, 21)
    endDate.setHours(23,59,59,999)
   Transactions.find({ date: { $gte: startDate.toJSON(), $lte: endDate.toJSON() } }, function (err, docs) {
        if (docs)
            res.send(docs)
    })
})
// Add new transaction
app.post('/new', function (req, res) {
   var newTransaction = req.body
    
    Transactions.insert(newTransaction, function (err, transaction) {
        if (err) 
            res.status(500).send(err)
        else {
            res.sendStatus(200)
            Inventory.decrementInventory(transaction.products)
        } 
    })
})
// GET a single transaction
app.get('/:transactionId', function (req, res) {
   Transactions.find({ _id: req.params.transactionId }, function (err, doc) {
        if (doc)
            res.send(doc[0])
    })
})

transaction.js Explained
The necessary dependencies are assigned to variables as was done previously.

A Transaction’s variable is created with filename and autoload using the nedbvariable Datastore as done earlier.

The app.get("/, function(req, res) function is the default path for the transactions database.

The app.get('/all', function (req, res) function retrieves all transactions from the transactions database.

The app.get('/limit', function (req, res) function retrieves transactions with specified limit.

The app.get('/day-total', function (req, res) function is gets total sales for the current day.

The app.get('/by-date', function (req, res) function is used to get transactions using a particular date

The app.post('/new', function (req, res)) function is used to add new a transaction

The app.get('/:transactionId', function (req, res) function is used to retrieve a single transaction.

To Start Node app from root directory using CLI , type command:

nodemon index.js

end in backend section

Frontend part

We are going to accomplish the following:

1.Creating a Template React app.
2.Creating Routes and Views with Code Description.

See here for source code
Frameworks we will be using:

axios is a Promise based HTTP client for the browser and node.js.

Bootstrap is a free open source library that contains HTML and CSS design templates for designing websites and web applications.

React-Bootstrap is a Bootstrap 3 components built with React.

moment is a lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates.

React is a JavaScript library for building user interfaces.

Creating a Template React App

Make sure you have Node and NPM installed.

Check Node and Npm Version via Command Line Interface (CLI)

node -v

npm -v

Access the real-time-pos-folder we used in part 1 Using CLI to create react app globally using npm:

For npm version 5.1 or earlier
npm install -g create-react-app

To create your app, run a single command
npm install create-react-app react-pos

For npm version 5.2+ and higher
npx install -g create-react-app

To create our appointment scheduler app, run a single command
npx install create-react-app react-pos

The Directory of your app will look something like this:

react-pos
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ └── favicon.ico
│ └── index.html
│ └── manifest.json
└── src
└── App.css
└── App.js
└── App.test.js
└── index.css
└── index.js
└── logo.svg
└── registerServiceWorker.js

To start the project in development mode via CLI
npm start

Access your app directory using:
cd react-pos

Install the following dependencies:
npm install bootstrap

npm install react-bootstrap

npm install axios

npm install moment

Creating Routes and Views

We are going to start by creating our routes

Start by editing your App.js in your root directory with following code:

import React from "react";
import Header from "./js/components/Header";
import Main from "./js/components/Main";
const App = () => (
  <div>
    <Main />
  </div>
);
export default App;

Also update your index.js in your root directory:

import React from "react";
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";
import registerServiceWorker from "./registerServiceWorker";
import "./index.css";
import "bootstrap/dist/css/bootstrap.css";
import { makeRoutes } from "./routes";
import App from "./App";
render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
registerServiceWorker();

You maybe wondering about the Main and Header Components, but we will create them shortly:

Create the following path in your “src” folder directory of your react-pos app:

js/components

Create Main.js in the js/components folder with the following code:

import React from "react";
import { Switch, Route } from "react-router-dom";
import Inventory from "./Inventory";
import Pos from "./Pos";
import Transactions from "./Transactions";
import LiveCart from "./LiveCart";
const Main = () => (
  <main>
    <Switch>
      <Route exact path="/" component={Pos} />
      <Route path="/inventory" component={Inventory} />
      <Route path="/transactions" component={Transactions} />
      <Route path="/livecart" component={LiveCart} />
    </Switch>
  </main>
);
export default Main;

Notice that our Main.js component is not a class; rather it is a functional component,. Arrow function to be precise. we are creating our routes using functions.

Lets create our Header.js component for navigation of our app

import React from "react";
import { Link } from "react-router-dom";
// The Header creates links that can be used to navigate
// between routes.
const Header = () => (
  <div className="text-center">
    <h1>
      <a href="/#/">Real Time Point POS</a>
    </h1>
 <ul className="nav-menu">
      <li className="lead">
        <Link to="/inventory">Inventory</Link>
      </li>
      <li className="lead">
        <Link to="/">POS</Link>
      </li>
      <li className="lead">
        <Link to="/transactions">Transactions</Link>
      </li>
      <li className="lead">
        <Link to="/livecart">LiveCart</Link>
      </li>
    </ul>
  </div>
);
export default Header;

you will notice as we continue that the Header component is included in all parent components.

Now lets create our views, Let’s start with the Inventory.js component in the src/js/component/ folder.

import React, { Component } from "react";
import "./App.css";
import Header from "./Header";
import Product from "./Product";
import axios from "axios";
const HOST = "http://localhost:80";
class Inventory extends Component {
  constructor(props) {
    super(props);
 this.state = { products: [] };
  }
  componentWillMount() {
    var url = HOST + `/api/inventory/products`;
    axios.get(url).then(response => {
      this.setState({ products: response.data });
    });
  }
  render() {
    var { products } = this.state;
 var renderProducts = () => {
      if (products.length === 0) {
        return <p>{products}</p>;
      }
      return products.map(product => <Product {...product} />);
    };
 return (
      <div>
        <Header />
 <div class="container">
          <a
            href="#/inventory/create-product"
            class="btn btn-success pull-right"
          >
            <i class="glyphicon glyphicon-plus" /> Add New Item
          </a>
          <br />
          <br />
 <table class="table">
            <thead>
              <tr>
                <th scope="col">Name</th>
                <th scope="col">Price</th>
                <th scope="col">Quantity on Hand</th>
                <th />
              </tr>
            </thead>
            <tbody>{renderProducts()}</tbody>
          </table>
        </div>
      </div>
    );
  }
}
export default Inventory;

Notice that We are using a class for the inventory component above. componentWillMount is a Lifecycle method which is used to modify the component state, in this particular situation we are retrieving products from the inventory database by through our Node.js Express App We created in part 1. the response is assigned to the product array using setState . All this is done before the page is fully loaded.

The render function will display our UI elements in the DOM (Document Object Model). The renderFunction checks the product array and display the result in the DOM.

Let’s move on to the POS.js Component. The Pos component will allows the user to add items to cart with prices. the cart will be updated in real time.

Create a Pos.js file in src/js/component/ folder:

import React, { Component } from "react";
import "./App.css";
import Header from "./Header";
import io from "socket.io-client";
import axios from "axios";
import moment from "moment";
import { Modal, Button } from "react-bootstrap";
import LivePos from "./LivePos";
const HOST = "http://localhost:80";
let socket = io.connect(HOST);
class Pos extends Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [],
      quantity: 1,
      id: 0,
      open: true,
      close: false,
      addItemModal: false,
      checkOutModal: false,
      amountDueModal: false,
      totalPayment: 0,
      total: 0,
      changeDue: 0,
      name: "",
      price: 0
    };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleName = this.handleName.bind(this);
    this.handlePrice = this.handlePrice.bind(this);
    this.handlePayment = this.handlePayment.bind(this);
    this.handleQuantityChange = this.handleQuantityChange.bind(this);
    this.handleCheckOut = this.handleCheckOut.bind(this);
  }
  componentDidUpdate() {
    if (this.state.items.length !== 0) {
      socket.emit("update-live-cart", this.state.items);
    }
  }
  handleSubmit = e => {
    e.preventDefault();
    this.setState({ addItemModal: false });
 const currentItem = {
      id: this.state.id++,
      name: this.state.name,
      price: this.state.price,
      quantity: this.state.quantity
    };
    var items = this.state.items;
    items.push(currentItem);
    this.setState({ items: items });
  };
  handleName = e => {
    this.setState({ name: e.target.value });
  };
  handlePrice = e => {
    this.setState({ price: e.target.value });
  };
  handlePayment = () => {
    this.setState({ checkOutModal: false });
    var amountDiff =
      parseInt(this.state.total, 10) - parseInt(this.state.totalPayment, 10);
    if (this.state.total <= this.state.totalPayment) {
      this.setState({ changeDue: amountDiff });
      this.setState({ receiptModal: true });
      this.handleSaveToDB();
      this.setState({ items: [] });
      this.setState({ total: 0 });
    } else {
      this.setState({ changeDue: amountDiff });
      this.setState({ amountDueModal: true });
    }
  };
  handleQuantityChange = (id, quantity) => {
    var items = this.state.items;
    for (var i = 0; i < items.length; i++) {
      if (items[i].id === id) {
        items[i].quantity = quantity;
        this.setState({ items: items });
      }
    }
  };
  handleCheckOut = () => {
    this.setState({ checkOutModal: true });
    var items = this.state.items;
    var totalCost = 0;
    for (var i = 0; i < items.length; i++) {
      var price = items[i].price * items[i].quantity;
      totalCost = parseInt(totalCost, 10) + parseInt(price, 10);
    }
    this.setState({ total: totalCost });
  };
  handleSaveToDB = () => {
    const transaction = {
      date: moment().format("DD-MMM-YYYY HH:mm:ss"),
      total: this.state.total,
      items: this.state.items
    };
    axios.post(HOST + "/api/new", transaction).catch(err => {
      console.log(err);
    });
  };
  render() {
    var { quantity, modal, items } = this.state;
 var renderAmountDue = () => {
      return (
        <Modal show={this.state.amountDueModal}>
          <Modal.Header closeButton>
            <Modal.Title>Amount</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <h3>
              Amount Due:
              <span class="text-danger">{this.state.changeDue}</span>
            </h3>
            <p>Customer payment incomplete; Correct and Try again</p>
          </Modal.Body>
          <Modal.Footer>
            <Button onClick={() => this.setState({ amountDueModal: false })}>
              close
            </Button>
          </Modal.Footer>
        </Modal>
      );
    };
    var renderReceipt = () => {
      return (
        <Modal show={this.state.receiptModal}>
          <Modal.Header closeButton>
            <Modal.Title>Receipt</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <h3>
              Total:
              <span class="text-danger">{this.state.totalPayment}</span>
            </h3>
            <h3>
              Change Due:
              <span class="text-success">{this.state.changeDue}</span>
            </h3>
          </Modal.Body>
          <Modal.Footer>
            <Button onClick={() => this.setState({ receiptModal: false })}>
              close
            </Button>
          </Modal.Footer>
        </Modal>
      );
    };
 var renderLivePos = () => {
      if (items.length === 0) {
        return <p> No products added</p>;
      } else {
        return items.map(
          item => (
            <LivePos {...item} onQuantityChange={this.handleQuantityChange} />
          ),
          this
        );
      }
    };
 return (
      <div>
        <Header />
        <div class="container">
          <div class="text-center">
            <span class="lead">Total</span>
            <br />
            <span class="text-success checkout-total-price">
              ${this.state.total}
              <span />
            </span>
            <div>
              <button
                class="btn btn-success lead"
                id="checkoutButton"
                onClick={this.handleCheckOut}
              >
                <i class="glyphicon glyphicon-shopping-cart" />
                <br />
                <br />
                C<br />
                h<br />
                e<br />
                c<br />
                k<br />
                o<br />
                u<br />
                t
              </button>
              <div className="modal-body">
                <Modal show={this.state.checkOutModal}>
                  <Modal.Header closeButton>
                    <Modal.Title>Checkout</Modal.Title>
                  </Modal.Header>
                  <Modal.Body>
                    <div ng-hide="transactionComplete" class="lead">
                      <h3>
                        Total:
                        <span class="text-danger"> {this.state.total} </span>
                      </h3>
 <form
                        class="form-horizontal"
                        name="checkoutForm"
                        onSubmit={this.handlePayment}
                      >
                        <div class="form-group">
                          <div class="input-group">
                            <div class="input-group-addon">$</div>
                            <input
                              type="number"
                              id="checkoutPaymentAmount"
                              class="form-control input-lg"
                              name="payment"
                              onChange={event =>
                                this.setState({
                                  totalPayment: event.target.value
                                })
                              }
                              min="0"
                            />
                          </div>
                        </div>
 <p class="text-danger">Enter payment amount.</p>
                        <div class="lead" />
                        <Button
                          class="btn btn-primary btn-lg lead"
                          onClick={this.handlePayment}
                        >
                          Print Receipt
                        </Button>
                      </form>
                    </div>
                  </Modal.Body>
                  <Modal.Footer>
                    <Button
                      onClick={() => this.setState({ checkOutModal: false })}
                    >
                      Close
                    </Button>
                  </Modal.Footer>
                </Modal>
              </div>
            </div>
          </div>
          {renderAmountDue()}
          {renderReceipt()}
          <table class="pos table table-responsive table-striped table-hover">
            <thead>
              <tr>
                <td colspan="6" class="text-center">
                  <span class="pull-left">
                    <button
                      onClick={() => this.setState({ addItemModal: true })}
                      class="btn btn-default btn-sm"
                    >
                      <i class="glyphicon glyphicon-plus" /> Add Item
                    </button>
                  </span>
                  <Modal show={this.state.addItemModal} onHide={this.close}>
                    <Modal.Header closeButton>
                      <Modal.Title>Add item(Product)</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                      <form
                        ref="form"
                        onSubmit={this.handleSubmit}
                        class="form-horizontal"
                      >
                        <div class="form-group">
                          <label class="col-md-2 lead" for="name">
                            Name
                          </label>
                          <div class="col-md-8 input-group">
                            <input
                              class="form-control"
                              name="name"
                              required
                              onChange={this.handleName}
                            />
                          </div>
                        </div>
                        <div class="form-group">
                          <label class="col-md-2 lead" for="price">
                            Price
                          </label>
                          <div class="col-md-8 input-group">
                            <div class="input-group-addon">$</div>
 <input
                              type="number"
                              step="any"
                              min="0"
                              onChange={this.handlePrice}
                              class="form-control"
                              name="price"
                              required
                            />
                          </div>
                        </div>
 <p class="text-danger">Enter price for item.</p>
                      </form>
                    </Modal.Body>
                    <Modal.Footer>
                      <Button onClick={this.handleSubmit}>Add</Button>
                      <Button
                        onClick={() => this.setState({ addItemModal: false })}
                      >
                        Cancel
                      </Button>
                    </Modal.Footer>
                  </Modal>
                </td>
              </tr>
              <tr class="titles">
                <th>Name</th>
                <th>Price</th>
                <th>Quantity</th>
                <th>Tax</th>
                <th>Total</th>
                <th />
              </tr>
            </thead>
            <tbody>{renderLivePos()}</tbody>
          </table>
        </div>
      </div>
    );
  }
}
export default Pos;

The Pos component enables the user to add items to cart, accept payment via checkout, print the receipt and saves to the database.

The componentDidUpdate Lifecycle method is used to check the state of the itemsarray everytime the component has been updated. if the item array contain one or more products the LiveCart is updated in real-time using socket.io.

The handleSubmit function adds an item to the item array.

The handlePrice function assigns the current price of an item to the price variable using the setState

The handleName function assigns the current name of an item to the name variable using the setState

The handlePayment function checks the amount the customer payment paid for the items against the total cost.

The handleQuantityChange function is a prop of the child component LivePos, it updates the quantity of an item when the user increases or reduces it.

The handleCheckout function calculates the total cost of items purchased by the customer and updates total using setState.

The renderLivePos function renders an item as it is added to the item array using the child component LivePos.

The renderReceipt displays a modal confirming payment.

The renderAmountDue display a modal to inform the user of incomplete payment.

The LivePos is a child component of the Pos component. it display each item as it added to the Pos component. The LivePos is also known as a Presentation component. check the source code this component

The handleSaveToDB function saves the transaction to the database

Let’s proceed to the Livecart component:

import React, { Component } from "react";
import "./App.css";
import io from "socket.io-client";
import Header from "./Header";
import axios from "axios";
import RecentTransactions from "./RecentTransactions";
import LiveTransactions from "./LiveTransactions";
import moment from "moment";
const HOST = "http://localhost:80";
var url = HOST + `/api//day-total/`;
class LiveCart extends Component {
  constructor(props) {
    super(props);
    this.state = { transactions: [], liveTransactions: [] };
  }
  componentWillMount() {
    // console.dir(socket);
    axios.get(url).then(response => {
      this.setState({ transactions: response.data });
      console.log("response", response.data);
    });
 var socket = io.connect(HOST);
 socket.on("update-live-cart-display", liveCart => {
      this.setState({ liveTransactions: liveCart });
    });
  }
  componentWillUnmount() {
    // socket.disconnect();
    // alert("Disconnecting Socket as component will unmount");
  }
  render() {
    var { transactions, liveTransactions } = this.state;
    var renderRecentTransactions = () => {
      if (transactions.length === 0) {
        return <p>No recent transactions available</p>;
      } else {
        return transactions.map(transaction => (
          <RecentTransactions {...transaction} />
        ));
      }
    };
    var renderDate = () => {
      return moment().format("DD-MMM-YYYY HH:mm:ss");
    };
    var renderLiveTransactions = () => {
      if (liveTransactions.length === 0) {
        return (
          <div>
            <div class="col-md-5 pull-right">
              <div>
                <div class="alert alert-warning text-center" role="alert">
                  <strong>Not Active:</strong> No items added at the moment.
                </div>
              </div>
            </div>
          </div>
        );
      } else {
        return liveTransactions.map(liveTransaction => (
          <LiveTransactions {...liveTransaction} />
        ));
      }
    };
    return (
      <div>
        <Header />
        <div class="livecart">
          <div class="col-md-5 pull-right">
            <div class="panel panel-primary">
              <div class="panel-heading text-center lead">{renderDate()}</div>
 <table class="receipt table table-hover">
                <thead>
                  <tr class="small">
                    <th> Quantity </th>
                    <th> Product </th>
                    <th> Price </th>
                  </tr>
                </thead>
                <tbody>{renderLiveTransactions()}</tbody>
              </table>
            </div>
          </div>
          <div class="col-md-5 pull-left">
            <div class="panel panel-default">
              <div class="panel-heading lead text-center">
                Recent Transactions
              </div>
 <div class="panel-body">
                <div class="text-center">
                  <span>Today's Sales</span>
                  <br />
                  <span class="text-success checkout-total-price">
                    $<span />
                  </span>
                </div>
 <table class="table table-hover table-striped">
                  <thead>
                    <tr>
                      <th>Time</th>
                      <th>Total</th>
                    </tr>
                  </thead>
                  <tbody>{renderRecentTransactions()}</tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}
export default LiveCart;

The LiveCart component renders recent and current transactions.

On ComponentWillMount recent transactions are retrieved, followed by current items on livecart using socket.io-client

render function display the user interface to DOM. renderRecentTransactions child
component is used to render recent transactions saved to the Database. renderLiveTransactions is also a child component used to render current transactions. Both renderRecentTransactions and renderLiveTransactionsare presentational components.

Let’s move on to the Transaction component:

import React, { Component } from "react";
import "./App.css";
import Header from "./Header";
import CompleteTransactions from "./CompleteTransactions";
import axios from "axios";
const HOST = "http://localhost:80";
const url = HOST + `/api/all`;
class Transactions extends Component {
  constructor(props) {
    super(props);
    this.state = { transactions: [] };
  }
  componentWillMount() {
    axios.get(url).then(response => {
      this.setState({ transactions: response.data });
      console.log("response:", response.data);
    });
  }
  render() {
    var { transactions } = this.state;
 var rendertransactions = () => {
      if (transactions.length === 0) {
        return <p>No Transactions found</p>;
      }
      return transactions.map(transaction => (
        <CompleteTransactions {...transaction} />
      ));
    };
 return (
      <div>
        <Header />
        <div class="text-center">
          <span class="">Today's Sales</span>
          <br />
          <span class="text-success checkout-total-price">
            $ <span />
          </span>
        </div>
 <br />
        <br />
 <table class="table table-hover table-striped">
          <thead>
            <tr>
              <th>Time</th>
              <th>Total</th>
              <th>Products</th>
              <th>Open</th>
            </tr>
          </thead>
          <tbody>{rendertransactions()}</tbody>
        </table>
      </div>
    );
  }
}
export default Transactions;

On the componentWillMount all transactions and retrieved from the database.

The rendertransactions function displays all the transactions using the CompleteTransactions presentional component. See source code for more on ‘CompleteTransactions.

We have succeeded in building The front and Backend of Real time Point of Sale System. I hope you had a blast.

The post Building simple Point of Sale system with Node.js & React.js appeared first on ReactNinja.

Latest comments (2)

Collapse
 
adrianducao profile image
Adrian Ducao

Is using Nodejs for POS system a great idea for computation and relational stuff?

Collapse
 
appplus profile image
Appplus

Hi Kris my name is Luis and I am looking for someone with your skill to help me develop an on demand delivery and a P.O.S like what you built here.. Please contact me back when available. Thanks