This tutorial will comprise of two parts:
Part 1 (Back End)
-
Frameworks description
-
Building the Node app from scratch
-
Testing with Postman
Part 2 (Front End)
-
Creating a Template React app.
-
Creating Routes and Views.
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 HyperText Transfer Protocol (HTTP).
Let's continue by creating the backend with Node.js, I will assume you have node and npm installed.
Part 1: Back End
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 server
from 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.js 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
an instance has two options filename
which specifies the path of the database and autoload, which automatically 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 nedb variable 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
And that is the Back End sorted!
Part 2: Front End
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.
Let's 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](http://localhost/)";
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](http://localhost/)";
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 items
array 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 for 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](http://localhost/)";
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 renderLiveTransactions
are 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](http://localhost/)";
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 the 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.
Get the source code and see a demo here.