├── 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 | 22 | Home 23 | About 24 | Contact 25 | 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 | ![Screenshot](https://user-images.githubusercontent.com/624592/47965141-0314ba00-e022-11e8-81e9-c825dad7722c.png) 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 |
40 | React-Woocommerce | ReactJS interfase using Ant Design for Woocommerce API 41 |
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 | 67 | { 68 | this.state.categories.map((category) => { 69 | return ( category.count > 0 && 70 | 71 | 72 | { 73 | category.image && 74 | {category.name} 75 | } 76 | { 77 | category.name + " (" + category.count + ")" 78 | } 79 | 80 | 81 | ); 82 | }) 83 | } 84 | 85 | 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 | 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 |
91 | 92 | 96 | {getFieldDecorator('name', { 97 | rules: [{required: true, message: 'Please input your name!', whitespace: true}], 98 | })( 99 | 100 | )} 101 | 102 | 103 | 106 | {getFieldDecorator('lastname', { 107 | rules: [{required: true, message: 'Please input your lastname!', whitespace: true}], 108 | })( 109 | 110 | )} 111 | 112 | 116 | {getFieldDecorator('email', { 117 | rules: [{ 118 | type: 'email', message: 'The input is not valid E-mail!', 119 | }, { 120 | required: true, message: 'Please input your E-mail!', 121 | }], 122 | })( 123 | 124 | )} 125 | 126 | 127 | 131 | {getFieldDecorator('phone', { 132 | rules: [{required: true, message: 'Please input your phone number!'}], 133 | })( 134 | 135 | )} 136 | 137 | 141 | {getFieldDecorator('payment_method', { 142 | rules: [{ 143 | required: true, message: 'Please select a payment method', 144 | }], 145 | })( 146 | 154 | )} 155 | 156 | 157 | 158 | {getFieldDecorator('agreement', { 159 | valuePropName: 'checked', 160 | })( 161 | I have read the agreement 162 | )} 163 | 164 | 165 | 166 | 167 |
168 | ); 169 | } 170 | } 171 | 172 | const WrappedRegistrationForm = Form.create()(CheckoutForm); 173 | 174 | export default WrappedRegistrationForm; 175 | --------------------------------------------------------------------------------