├── public
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── img
│ └── no_image.jpg
├── App.css
├── App.test.js
├── components
│ ├── Shopping.js
│ ├── AboutUs.js
│ ├── Checkout.js
│ ├── Layout
│ │ ├── AppHeader.js
│ │ └── SideMenu.js
│ ├── ShoppingCart.js
│ ├── ProductList.js
│ └── CheckoutForm.js
├── index.js
├── actions.js
├── remotes
│ └── woocommerce.js
├── App.js
├── store.js
└── serviceWorker.js
├── .env.default
├── .gitignore
├── package.json
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterPan627/react-woocommerce/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/img/no_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterPan627/react-woocommerce/HEAD/src/img/no_image.jpg
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | float:left;
3 | margin-right: 20px;
4 | }
5 |
6 | .logo img {
7 | height: 30px;
8 | }
9 |
--------------------------------------------------------------------------------
/.env.default:
--------------------------------------------------------------------------------
1 | REACT_APP_WOOCOMMERCE_API_ENDPOINT = "https://www.example.com/wp-json/wc/v1/"
2 | REACT_APP_WOOCOMMERCE_API_CLIENT = "CCCCCC"
3 | REACT_APP_WOOCOMMERCE_API_SECRET = "SSSSSS"
4 | REACT_APP_WOOCOMMERCE_PRODUCTS_PER_PAGE = 12
5 | REACT_APP_WOOCOMMERCE_CATEGORIES_PER_PAGE = 100
6 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/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 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.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 |
25 | .idea
26 |
27 | .env
28 |
--------------------------------------------------------------------------------
/src/components/Shopping.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Row, Col} from 'antd';
3 | import ShoppingCart from './ShoppingCart';
4 | import ProductList from './ProductList';
5 |
6 | const Shopping = (props) => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default Shopping;
25 |
--------------------------------------------------------------------------------
/src/components/AboutUs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | const AboutUs = () => {
5 | return (
6 |
7 |
About Us
8 |
9 |
Contributors to this project:
10 |
11 |
15 |
16 | );
17 | };
18 |
19 |
20 | export default AboutUs;
21 |
--------------------------------------------------------------------------------
/src/components/Checkout.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Row, Col} from 'antd';
3 | import ShoppingCart from './ShoppingCart';
4 | import CheckoutForm from './CheckoutForm'
5 |
6 | class Checkout extends Component {
7 |
8 | render() {
9 |
10 | return (
11 |
12 |
13 |
14 | Checkout
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | export default Checkout;
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-woocommerce",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^3.10.3",
7 | "axios": "^0.18.0",
8 | "bootstrap": "^4.1.3",
9 | "dotenv": "^6.1.0",
10 | "react": "^16.6.0",
11 | "react-dom": "^16.6.0",
12 | "react-redux": "^5.1.0",
13 | "react-router-dom": "^4.3.1",
14 | "react-scripts": "2.1.1",
15 | "redux": "^4.0.1",
16 | "redux-thunk": "^2.3.0"
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": "react-app"
26 | },
27 | "browserslist": [
28 | ">0.2%",
29 | "not dead",
30 | "not ie <= 11",
31 | "not op_mini all"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
5 | import './App.css';
6 | import * as serviceWorker from './serviceWorker';
7 |
8 | import store from './store';
9 | import {Provider} from 'react-redux';
10 | import {loadCartProducts} from "./actions";
11 |
12 | require('dotenv').config();
13 |
14 | store.dispatch(loadCartProducts());
15 |
16 | ReactDOM.render(
17 |
18 |
19 | ,
20 | document.getElementById('root'));
21 |
22 | // If you want your app to work offline and load faster, you can change
23 | // unregister() to register() below. Note this comes with some pitfalls.
24 | // Learn more about service workers: http://bit.ly/CRA-PWA
25 | serviceWorker.unregister();
26 |
--------------------------------------------------------------------------------
/src/actions.js:
--------------------------------------------------------------------------------
1 | const addToCart = (product) => {
2 | //Save just the important
3 | var p = {};
4 |
5 | p.id = product.id;
6 | p.name = product.name;
7 | p.price = parseFloat(product.price);
8 | p.images = product.images;
9 |
10 | return {
11 | type: 'ADD_TO_CART',
12 | product: p
13 | }
14 | };
15 |
16 | const removeFromCart = (product) => {
17 | return {
18 | type: 'REMOVE_FROM_CART',
19 | product
20 | }
21 | };
22 |
23 | const removeProductItem = (product) => {
24 | return {
25 | //DECREASE PRODUCT CART
26 | type: 'REMOVE_PRODUCT_ITEM',
27 | product
28 | }
29 | };
30 |
31 | const substractProduct = (product) => {
32 | return {
33 | type: 'SUBSTRACT_FROM_CART',
34 | product
35 | }
36 | };
37 |
38 | const loadCartProducts = () => {
39 | return {
40 | type: 'LOAD_CART_PRODUCTS',
41 | }
42 | };
43 |
44 | export { addToCart, removeFromCart, substractProduct, loadCartProducts, removeProductItem };
45 |
--------------------------------------------------------------------------------
/src/components/Layout/AppHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout, Menu } from 'antd';
3 | import { Link } from 'react-router-dom';
4 |
5 | const {Header} = Layout;
6 |
7 | const AppHeader = () => {
8 | return (
9 |
10 |
11 |
12 |

14 |
15 |
16 |
26 |
27 | );
28 | };
29 |
30 | export default AppHeader;
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shopping Cart: ReactJS + Woocommerce
2 |
3 |
4 | ## Description
5 | This project aims to be a web/mobile interfase to enhance user experience on Woocommerce platform. It is a ReactJS application that uses the Rest API provided by Woocommerce to display categories, products, and create orders.
6 |
7 | 
8 |
9 | ## Installation
10 | You need to have a Woocommerce instance running and also created a ``client_id`` and ``client_secret`` from the Wocommerce Interfase. [Check this link for extra information](https://docs.woocommerce.com/document/woocommerce-rest-api/#section-3)
11 |
12 | - Clone this repository
13 | - Run ``npm install``
14 | - Copy default config file ``cp .env.default .env``
15 | - Edit ``.env`` and fill with your generated API keys
16 | - Save and run ``npm start``
17 |
18 | ## Development
19 | This is a free time project so it's currently on development. Not ready for production.
20 | If you want to participate, feel free to contact me.
21 |
22 | ## Woocommerce Notes
23 |
24 | In order to avoid problems like :
25 |
26 | ```
27 | Access to XMLHttpRequest at 'https://HOST/wp-json/wc/v3/products/categories?per_page=100' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field X-XSRF-TOKEN is not allowed by Access-Control-Allow-Headers in preflight response.
28 |
29 | ```
30 |
31 | You should add you the Woocommerce headers this line:
32 |
33 | ```
34 | header('Access-Control-Allow-Headers: Content-Type, x-xsrf-token, x_csrftoken');
35 | ```
36 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | ReactJS + Woocommerce
24 |
25 |
26 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/remotes/woocommerce.js:
--------------------------------------------------------------------------------
1 | import axios from "axios/index";
2 |
3 | /**
4 | * Default headers for doing API request to Woocommerce using API v3 and basic auth
5 | * @type {{params: {}, withCredentials: boolean, auth: {username: *, password: *}}}
6 | */
7 | let defaultHeaders = {
8 | params: {},
9 | withCredentials: true,
10 | auth: {
11 | username: process.env.REACT_APP_WOOCOMMERCE_API_CLIENT,
12 | password: process.env.REACT_APP_WOOCOMMERCE_API_SECRET
13 | }
14 | };
15 |
16 | /**
17 | * Retrieves category tree
18 | * @returns {AxiosPromise}
19 | */
20 | const getCategories = () => {
21 | defaultHeaders.params = {
22 | per_page: process.env.REACT_APP_WOOCOMMERCE_CATEGORIES_PER_PAGE
23 | };
24 |
25 | return axios.get(`${process.env.REACT_APP_WOOCOMMERCE_API_ENDPOINT}/wp-json/wc/v3/products/categories`, defaultHeaders);
26 | };
27 |
28 | /**
29 | * Retrieves products with category_id given as parameter
30 | * @param category_id
31 | * @returns {AxiosPromise}
32 | */
33 | const getProductsByCategory = (category, page, per_page) => {
34 |
35 | defaultHeaders.params = {
36 | orderby: 'title',
37 | order: 'asc',
38 | status: 'publish',
39 | category,
40 | per_page,
41 | page,
42 | };
43 |
44 | return axios.get(`${process.env.REACT_APP_WOOCOMMERCE_API_ENDPOINT}/wp-json/wc/v3/products`, defaultHeaders)
45 | };
46 |
47 | const getCategoryById = (category_id) => {
48 |
49 | return axios.get(`${process.env.REACT_APP_WOOCOMMERCE_API_ENDPOINT}/wp-json/wc/v3/products/categories/${category_id}`, defaultHeaders)
50 | };
51 |
52 | /**
53 | * Get payment method info for checkout
54 | * @returns {AxiosPromise}
55 | */
56 | const getPaymentInfo = () => {
57 | return axios.get(`${process.env.REACT_APP_WOOCOMMERCE_API_ENDPOINT}/wp-json/wc/v3/payment_gateways`, defaultHeaders);
58 | };
59 |
60 |
61 | export {getPaymentInfo, getProductsByCategory, getCategories, getCategoryById};
62 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component, Fragment} from 'react';
2 | import {BrowserRouter as Router, Route} from "react-router-dom";
3 | import {Layout} from 'antd';
4 |
5 | import AppHeader from './components/Layout/AppHeader';
6 | import SideMenu from './components/Layout/SideMenu';
7 | import AboutUs from './components/AboutUs';
8 | import Shopping from "./components/Shopping";
9 | import Checkout from "./components/Checkout";
10 |
11 |
12 | const {Content, Footer} = Layout;
13 |
14 | const Index = () => Home
;
15 | const Contact = () => Contact Us
;
16 |
17 | class App extends Component {
18 | render() {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | export default App;
52 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, combineReducers } from 'redux';
2 | import thunk from 'redux-thunk';
3 |
4 | /**
5 | * Cart function
6 | * @param state
7 | * @param action
8 | * @returns {*}
9 | */
10 | const cart = (state = [], action) => {
11 |
12 | if (action.type === 'LOAD_CART_PRODUCTS') {
13 |
14 | try {
15 | return JSON.parse(localStorage.getItem('cart')) || [];
16 | } catch (e) {
17 | localStorage.setItem('cart', JSON.stringify([]));
18 | return [];
19 | }
20 |
21 |
22 | } else if (action.type === 'ADD_TO_CART') {
23 |
24 | const { product } = action;
25 | let exists = false;
26 |
27 | const changed = state.map((e) => {
28 |
29 | if (e.id === product.id) {
30 | exists = true;
31 | e.qty++;
32 | }
33 |
34 | return e;
35 | });
36 |
37 | if (!exists) {
38 | product.qty = 1;
39 | return state.concat(product);
40 | }
41 |
42 | return changed;
43 |
44 |
45 | } else if (action.type === 'REMOVE_FROM_CART') {
46 | return state.filter((e) => {
47 | return e.id !== action.product.id;
48 | });
49 | } else if (action.type === 'REMOVE_PRODUCT_ITEM') {
50 | return state.map(e => {
51 | if (e.id === action.product.id && e.qty > 0) {
52 | e.qty--;
53 | }
54 | return e;
55 | });
56 | }
57 |
58 | return state;
59 | };
60 |
61 | /**
62 | * Logger middleware
63 | * @param store
64 | * @returns {function(*): function(*=): *}
65 | */
66 | const logger = (store) => (next) => (action) => {
67 | // console.log("dispatching", action);
68 | let result = next(action);
69 | // console.log("next state", store.getState());
70 | return result;
71 | };
72 |
73 |
74 | const updateLocalStorageCart = (store) => (next) => (action) => {
75 | let result = next(action);
76 |
77 | if (action.type === 'ADD_TO_CART' ||
78 | action.type === 'REMOVE_FROM_CART' ||
79 | action.type === 'REMOVE_PRODUCT_ITEM') {
80 |
81 | try {
82 | localStorage.setItem('cart', JSON.stringify(store.getState()['cart']));
83 | } catch (e) {
84 |
85 | console.log("Error trying to set cart", e);
86 | localStorage.setItem('cart', JSON.stringify([]));
87 |
88 | }
89 |
90 | }
91 | return result;
92 | };
93 |
94 |
95 |
96 | export default createStore(combineReducers({
97 | cart,
98 | }), applyMiddleware(logger, thunk, updateLocalStorageCart));
99 |
100 |
--------------------------------------------------------------------------------
/src/components/Layout/SideMenu.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Layout, Menu, Spin} from 'antd';
3 | import {Link} from 'react-router-dom';
4 | import {getCategories} from "../../remotes/woocommerce";
5 |
6 | const {Sider} = Layout;
7 |
8 |
9 | const SideStyle = {
10 | textAlign: 'center'
11 | };
12 |
13 | const categoryImage = {
14 | width: '30px',
15 | height: '30px',
16 | marginRight: '10px'
17 | };
18 |
19 |
20 | class SideMenu extends Component {
21 |
22 | constructor(props, state) {
23 | super(props, state);
24 |
25 | this.state = {
26 |
27 | loading: true,
28 | categories: []
29 |
30 | };
31 |
32 | }
33 |
34 | componentDidMount() {
35 |
36 |
37 | getCategories().then(response => {
38 |
39 | this.setState({
40 | categories: response.data,
41 | loading: false
42 | });
43 |
44 | });
45 | }
46 |
47 | renderCategories = () => {
48 |
49 | if (this.state.loading) {
50 | return (
51 |
52 |
53 |
54 | );
55 | }
56 |
57 | if (!this.state.categories || this.state.categories.length === 0) {
58 | return No categories to show
59 | }
60 |
61 | return (
62 |
86 | );
87 |
88 | };
89 |
90 | render() {
91 |
92 | return (
93 |
96 |
97 | Categories
98 |
99 | {this.renderCategories()}
100 |
101 |
102 |
103 | );
104 | }
105 | }
106 |
107 | export default SideMenu;
108 |
--------------------------------------------------------------------------------
/src/components/ShoppingCart.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from "react";
2 | import { connect } from "react-redux";
3 | import { removeFromCart, removeProductItem, addToCart } from "../actions";
4 | import { Affix, Card, List, Avatar, Button, Popconfirm } from "antd";
5 | import { Link } from "react-router-dom";
6 |
7 | class ShoppingCart extends Component {
8 | renderCheckoutButton = total => {
9 | if (total > 0) {
10 | return Checkout;
11 | }
12 |
13 | return Minumum amount to checkout = 500
;
14 | };
15 |
16 | renderProductList = () => {
17 | const total = this.props.cart.reduce(
18 | (prev, curr) => prev + curr.qty * curr.price,
19 | 0
20 | );
21 |
22 | return (
23 |
24 | (
28 | 1 &&
31 |
58 | )
59 | }
60 | />
61 | < h2 > Total: $ {total}
62 | {this.renderCheckoutButton(total)}
63 |
64 | );
65 | };
66 |
67 | render() {
68 | return (
69 |
70 |
71 | {this.renderProductList()}
72 |
73 |
74 | );
75 | }
76 | }
77 |
78 | const mapStateToProps = state => {
79 | return {
80 | cart: state.cart
81 | };
82 | };
83 |
84 | const mapDispatchToProps = dispatch => {
85 | return {
86 | removeFromCart(product) {
87 | dispatch(removeFromCart(product));
88 | },
89 | removeProductItem(product) {
90 | dispatch(removeProductItem(product));
91 | },
92 | addToCart(product) {
93 | dispatch(addToCart(product));
94 | }
95 | };
96 | };
97 |
98 | export default connect(
99 | mapStateToProps,
100 | mapDispatchToProps
101 | )(ShoppingCart);
102 |
--------------------------------------------------------------------------------
/src/components/ProductList.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react';
2 | import { Spin, List, Card, Button, Pagination, Row, Col } from 'antd';
3 | import { connect } from 'react-redux';
4 | import { addToCart } from "../actions";
5 | import { getProductsByCategory, getCategoryById } from "../remotes/woocommerce";
6 | import no_image from '../img/no_image.jpg'
7 |
8 | const { Meta } = Card;
9 |
10 | class ProductList extends Component {
11 |
12 | constructor(props, state) {
13 | super(props, state);
14 |
15 | this.state = {
16 | category_id: null,
17 | products: [],
18 | loading: false,
19 | currentPage: 1,
20 | category: []
21 | }
22 | }
23 |
24 | fetchCategory = category_id => {
25 | this.setState({
26 | loading: true,
27 | });
28 |
29 | getCategoryById(category_id).then(response => {
30 | this.setState({
31 | category: response.data,
32 | loading: false,
33 | });
34 | });
35 | }
36 |
37 | fetchProducts = (category_id, page, per_page) => {
38 | this.setState({
39 | loading: true,
40 | products: [],
41 | category_id
42 | });
43 |
44 | getProductsByCategory(category_id, page, per_page).then(response => {
45 | this.setState({
46 | products: response.data,
47 | loading: false,
48 | currentPage: page
49 | });
50 | });
51 | };
52 |
53 | componentDidUpdate() {
54 | if (this.state.category_id !== this.props.match.params.id) {
55 | this.fetchCategory(this.props.match.params.id);
56 | this.fetchProducts(this.props.match.params.id, 1, process.env.REACT_APP_WOOCOMMERCE_PRODUCTS_PER_PAGE);
57 | }
58 | }
59 |
60 | renderPrices = (item) => {
61 | if (parseFloat(item.price) === parseFloat(item.regular_price)) {
62 | return $ {item.price};
63 | } else {
64 | var discount = (1 - (parseInt(item.price)/parseInt(item.regular_price)))*100;
65 | return ({`${discount.toFixed(0)}%`} | $ {item.regular_price} | $ {item.price});
66 | }
67 | };
68 |
69 | onChange = (page) => {
70 | this.fetchProducts(this.state.category_id, page, process.env.REACT_APP_WOOCOMMERCE_PRODUCTS_PER_PAGE);
71 | }
72 |
73 | renderProducts = () => {
74 | if (this.state.loading) {
75 | return (
76 |
77 | );
78 | }
79 |
80 | if (!this.state.products || this.state.products.length === 0) {
81 | return No products in this category
82 | }
83 |
84 | return (
85 |
86 |
87 |
88 | `${range[0]}-${range[1]} of ${total} items`} defaultCurrent={this.state.currentPage} total={this.state.category.count} onChange={this.onChange}/>
89 |
90 |
91 |
92 |
93 | (
97 |
98 | {
99 | item.price > 0 &&
100 | }
103 | actions={[
104 | this.props.addToCart(item)}>Add to Cart
105 | ]}>
106 |
110 |
111 | }
112 |
113 | )}
114 | />
115 |
116 |
117 |
118 | );
119 |
120 | };
121 |
122 | render() {
123 |
124 | return (
125 |
126 |
Products on {this.state.category.name}
127 | {this.renderProducts()}
128 |
129 | );
130 | }
131 | }
132 |
133 | const mapDispatchToProps = (dispatch) => {
134 | return {
135 | addToCart(product) {
136 | dispatch(addToCart(product));
137 | }
138 | }
139 | };
140 |
141 | export default connect(null, mapDispatchToProps)(ProductList);
142 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/CheckoutForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Form, Input, Select, Checkbox, Button} from 'antd';
3 | import {getPaymentInfo} from "../remotes/woocommerce";
4 |
5 | const FormItem = Form.Item;
6 | const Option = Select.Option;
7 |
8 | class CheckoutForm extends React.Component {
9 | state = {
10 | confirmDirty: false,
11 | payment_methods: []
12 | };
13 |
14 | componentDidMount() {
15 |
16 | getPaymentInfo().then(response => {
17 | this.setState({
18 | payment_methods: response.data
19 | });
20 | });
21 |
22 | }
23 |
24 |
25 | handleSubmit = (e) => {
26 | e.preventDefault();
27 | this.props.form.validateFieldsAndScroll((err, values) => {
28 | if (!err) {
29 | console.log('Received values of form: ', values);
30 | }
31 | });
32 | };
33 |
34 | handleConfirmBlur = (e) => {
35 | const value = e.target.value;
36 | this.setState({confirmDirty: this.state.confirmDirty || !!value});
37 | };
38 |
39 | compareToFirstPassword = (rule, value, callback) => {
40 | const form = this.props.form;
41 | if (value && value !== form.getFieldValue('password')) {
42 | callback('Two passwords that you enter is inconsistent!');
43 | } else {
44 | callback();
45 | }
46 | };
47 |
48 | validateToNextPassword = (rule, value, callback) => {
49 | const form = this.props.form;
50 | if (value && this.state.confirmDirty) {
51 | form.validateFields(['confirm'], {force: true});
52 | }
53 | callback();
54 | };
55 |
56 | render() {
57 | const {getFieldDecorator} = this.props.form;
58 |
59 | const formItemLayout = {
60 | labelCol: {
61 | xs: {span: 24},
62 | sm: {span: 8},
63 | },
64 | wrapperCol: {
65 | xs: {span: 24},
66 | sm: {span: 16},
67 | },
68 | };
69 | const tailFormItemLayout = {
70 | wrapperCol: {
71 | xs: {
72 | span: 24,
73 | offset: 0,
74 | },
75 | sm: {
76 | span: 16,
77 | offset: 8,
78 | },
79 | },
80 | };
81 | const prefixSelector = getFieldDecorator('prefix', {
82 | initialValue: '54',
83 | })(
84 |
87 | );
88 |
89 | return (
90 |
168 | );
169 | }
170 | }
171 |
172 | const WrappedRegistrationForm = Form.create()(CheckoutForm);
173 |
174 | export default WrappedRegistrationForm;
175 |
--------------------------------------------------------------------------------