├── .gitignore
├── README.md
├── db.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.js
├── components
└── ComponentToPrint.jsx
├── index.css
├── index.js
├── layouts
└── MainLayout.jsx
└── pages
├── HomePage.jsx
└── POSPage.jsx
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Tutorial - Create a Point of Sale System for Beginners in 1 Hour (React POS)
2 |
3 | This project was built for teaching React for beginners in 1 Hour. Students learn how to set up a real world react project and build a Point of Sale System. The video for this tutorial is available on Devtamin's Youtube Channel, [https://www.youtube.com/watch?v=8E7Xwy0lXlg](https://www.youtube.com/watch?v=8E7Xwy0lXlg). Please, enjoy the video and subscribe Devtamin on Youtube.
4 |
5 | ## How to set up the project
6 |
7 | - Clone this respository to your computer
8 | - Access to the project folder on the computer via Termaial or Git Bash
9 | - Excute `npm install` to download all necessary packages
10 | - Excute `npm start` to start the project
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "products": [
3 | {
4 | "id": 123,
5 | "name": "orange",
6 | "price": "2",
7 | "image": "https://cdn.pixabay.com/photo/2017/01/20/15/06/oranges-1995056_960_720.jpg"
8 | },
9 | {
10 | "id": 131,
11 | "name": "Milk",
12 | "price": "3",
13 | "image": "https://cdn.pixabay.com/photo/2018/03/16/16/42/milk-3231772_960_720.jpg"
14 | },
15 | {
16 | "id": 132,
17 | "name": "Ice Cream",
18 | "price": "4",
19 | "image": "https://cdn.pixabay.com/photo/2016/12/26/16/09/bowl-1932375_960_720.jpg"
20 | },
21 | {
22 | "id": 133,
23 | "name": "Salmon",
24 | "price": "10",
25 | "image": "https://cdn.pixabay.com/photo/2021/01/05/23/18/salmon-5892659_960_720.jpg"
26 | },
27 | {
28 | "id": 134,
29 | "name": "Watermelon",
30 | "price": "2",
31 | "image": "https://cdn.pixabay.com/photo/2015/09/27/18/18/watermelons-961128_960_720.jpg"
32 | },
33 | {
34 | "id": 135,
35 | "name": "Potato",
36 | "price": "4",
37 | "image": "https://cdn.pixabay.com/photo/2016/08/11/08/43/potatoes-1585060_960_720.jpg"
38 | }
39 | ]
40 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.1.1",
8 | "@testing-library/user-event": "^13.5.0",
9 | "axios": "^0.27.2",
10 | "react": "^18.1.0",
11 | "react-dom": "^18.1.0",
12 | "react-router-dom": "^6.3.0",
13 | "react-scripts": "5.0.1",
14 | "react-to-print": "^2.14.7",
15 | "react-toastify": "^8.2.0",
16 | "web-vitals": "^2.1.4"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": [
26 | "react-app",
27 | "react-app/jest"
28 | ]
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | },
42 | "proxy": "http://localhost:5000"
43 | }
44 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phithounsavanh/react-pos/2f5be155f85852cefdfb06b18534b8095a688337/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 |
28 | React App
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phithounsavanh/react-pos/2f5be155f85852cefdfb06b18534b8095a688337/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phithounsavanh/react-pos/2f5be155f85852cefdfb06b18534b8095a688337/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | BrowserRouter as Router,
3 | Routes,
4 | Route,
5 | } from 'react-router-dom';
6 | import HomePage from './pages/HomePage';
7 | import POSPage from './pages/POSPage';
8 |
9 | function App() {
10 | return (
11 |
12 |
13 | } />
14 | } />
15 |
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/src/components/ComponentToPrint.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const ComponentToPrint = React.forwardRef((props, ref) => {
4 | const {cart, totalAmount} = props;
5 | return (
6 |
7 |
8 |
9 |
10 | # |
11 | Name |
12 | Price |
13 | Qty |
14 | Total |
15 |
16 |
17 |
18 | { cart ? cart.map((cartProduct, key) =>
19 | {cartProduct.id} |
20 | {cartProduct.name} |
21 | {cartProduct.price} |
22 | {cartProduct.quantity} |
23 | {cartProduct.totalAmount} |
24 |
25 |
26 |
)
27 |
28 | : ''}
29 |
30 |
31 |
Total Amount: ${totalAmount}
32 |
33 | );
34 | });
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
15 | .pos-item{
16 | cursor: pointer;
17 | font-weight: bold;
18 | }
19 |
20 | .pos-item:hover{
21 | background: rgb(2, 85, 162);
22 | color: white;
23 | }
24 |
25 | .pos-item img{
26 | width: 100%;
27 | max-height: 100px;
28 | object-fit: cover;
29 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root'));
7 | root.render(
8 |
9 | );
10 |
11 |
--------------------------------------------------------------------------------
/src/layouts/MainLayout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Link} from 'react-router-dom'
3 | import { ToastContainer } from 'react-toastify';
4 | import 'react-toastify/dist/ReactToastify.css';
5 |
6 | function MainLayout({children}) {
7 | return (
8 |
9 |
10 |
15 |
16 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
26 | export default MainLayout
--------------------------------------------------------------------------------
/src/pages/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Link} from 'react-router-dom'
3 | import MainLayout from '../layouts/MainLayout'
4 |
5 | function HomePage() {
6 | return (
7 |
8 |
9 |
Welcome to the simple POS for small buiness
10 |
Labore tempor ipsum duis ea exercitation laboris laborum mollit qui exercitation.
11 |
If you have an issue, call 443-444-xxxx anytimes
12 |
Click here to sell products
13 |
14 |
15 | )
16 | }
17 |
18 | export default HomePage
--------------------------------------------------------------------------------
/src/pages/POSPage.jsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef, useState} from 'react'
2 | import MainLayout from '../layouts/MainLayout'
3 | import axios from "axios"
4 | import { toast } from 'react-toastify';
5 | import { ComponentToPrint } from '../components/ComponentToPrint';
6 | import { useReactToPrint } from 'react-to-print';
7 |
8 | function POSPage() {
9 |
10 | const [products, setProducts] = useState([]);
11 | const [isLoading, setIsLoading] = useState(false);
12 | const [cart, setCart] = useState([]);
13 | const [totalAmount, setTotalAmount] = useState(0);
14 |
15 | const toastOptions = {
16 | autoClose: 400,
17 | pauseOnHover: true,
18 | }
19 |
20 | const fetchProducts = async() => {
21 | setIsLoading(true);
22 | const result = await axios.get('products');
23 | setProducts(await result.data);
24 | setIsLoading(false);
25 | }
26 |
27 | const addProductToCart = async(product) =>{
28 | // check if the adding product exist
29 | let findProductInCart = await cart.find(i=>{
30 | return i.id === product.id
31 | });
32 |
33 | if(findProductInCart){
34 | let newCart = [];
35 | let newItem;
36 |
37 | cart.forEach(cartItem => {
38 | if(cartItem.id === product.id){
39 | newItem = {
40 | ...cartItem,
41 | quantity: cartItem.quantity + 1,
42 | totalAmount: cartItem.price * (cartItem.quantity + 1)
43 | }
44 | newCart.push(newItem);
45 | }else{
46 | newCart.push(cartItem);
47 | }
48 | });
49 |
50 | setCart(newCart);
51 | toast(`Added ${newItem.name} to cart`,toastOptions)
52 |
53 | }else{
54 | let addingProduct = {
55 | ...product,
56 | 'quantity': 1,
57 | 'totalAmount': product.price,
58 | }
59 | setCart([...cart, addingProduct]);
60 | toast(`Added ${product.name} to cart`, toastOptions)
61 | }
62 |
63 | }
64 |
65 | const removeProduct = async(product) =>{
66 | const newCart =cart.filter(cartItem => cartItem.id !== product.id);
67 | setCart(newCart);
68 | }
69 |
70 | const componentRef = useRef();
71 |
72 | const handleReactToPrint = useReactToPrint({
73 | content: () => componentRef.current,
74 | });
75 |
76 | const handlePrint = () => {
77 | handleReactToPrint();
78 | }
79 |
80 | useEffect(() => {
81 | fetchProducts();
82 | },[]);
83 |
84 | useEffect(() => {
85 | let newTotalAmount = 0;
86 | cart.forEach(icart => {
87 | newTotalAmount = newTotalAmount + parseInt(icart.totalAmount);
88 | })
89 | setTotalAmount(newTotalAmount);
90 | },[cart])
91 |
92 |
93 |
94 | return (
95 |
96 |
97 |
98 | {isLoading ? 'Loading' :
99 | {products.map((product, key) =>
100 |
101 |
addProductToCart(product)}>
102 |
{product.name}
103 |

104 |
${product.price}
105 |
106 |
107 |
108 | )}
109 |
}
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | # |
121 | Name |
122 | Price |
123 | Qty |
124 | Total |
125 | Action |
126 |
127 |
128 |
129 | { cart ? cart.map((cartProduct, key) =>
130 | {cartProduct.id} |
131 | {cartProduct.name} |
132 | {cartProduct.price} |
133 | {cartProduct.quantity} |
134 | {cartProduct.totalAmount} |
135 |
136 |
137 | |
138 |
139 |
)
140 |
141 | : 'No Item in Cart'}
142 |
143 |
144 |
Total Amount: ${totalAmount}
145 |
146 |
147 |
148 | { totalAmount !== 0 ?
149 |
152 |
153 |
: 'Please add a product to the cart'
154 |
155 | }
156 |
157 |
158 |
159 |
160 |
161 |
162 | )
163 | }
164 |
165 | export default POSPage
--------------------------------------------------------------------------------