├── .babelrc ├── .gitignore ├── README.md ├── client ├── components │ ├── Header.js │ ├── Home.js │ ├── Post.js │ └── Products.js ├── index.js ├── routes.js └── store │ ├── actions │ └── actions.js │ ├── reducers │ └── rootReducer.js │ └── store.js ├── config.example.js ├── config.js ├── helper └── template.js ├── package-lock.json ├── package.json ├── server └── index.js ├── webpack.base.js ├── webpack.client.js ├── webpack.server.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.log 3 | npm-debug.log 4 | yarn-error.log 5 | .DS_Store 6 | build/ 7 | node_modules/ 8 | dist/ 9 | .cache 10 | config.js 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React WooCommerce Theme :electric_plug: 2 | > * This is a React WooCommerce theme, built with Webpack, Babel, Node, Express, using WooCommerce REST API. 3 | 4 | 5 | ## Getting Started :rocket: 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development purposes. 8 | 9 | ### Prerequisites :page_facing_up: 10 | 11 | 12 | ### Installing :wrench: 13 | 14 | 1. Clone this repo in `git clone https://github.com/imranhsayed/react-woocommerce-theme` 15 | 2. `cd react-woocommerce-theme` 16 | 3. `npm install` 17 | 18 | ## Use :point_right: 19 | 20 | * Rename `congif.example.js` to `config.js` and update your WooCommerce config keys 21 | 22 | ```ruby 23 | const keys = { 24 | consumerKey: 'xxxxx', 25 | consumerSecret: 'xxxxx', 26 | }; 27 | ``` 28 | 29 | ## Common Commands :computer: 30 | 31 | * `dev` Runs webpack dev server in watch mode for both client and server in watch mode. 32 | 33 | ## Contributing :busts_in_silhouette: 34 | 35 | Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us. 36 | 37 | ## Versioning :bookmark_tabs: 38 | 39 | I use [Git](https://github.com/) for versioning. 40 | 41 | ## Author :bust_in_silhouette: 42 | 43 | * **[Imran Sayed](https://imransayed.com)** 44 | 45 | ## License :page_with_curl: 46 | 47 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 48 | -------------------------------------------------------------------------------- /client/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | class Header extends Component { 5 | render() { 6 | return ( 7 |
8 | Home 9 | Post 10 | Products 11 |
12 | ); 13 | } 14 | } 15 | 16 | export default Header; 17 | -------------------------------------------------------------------------------- /client/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Home extends Component { 4 | render() { 5 | return ( 6 |
7 | Hello world from React SSR. This is Home.. 8 |
9 | ); 10 | } 11 | } 12 | 13 | export default { 14 | component: Home 15 | }; 16 | -------------------------------------------------------------------------------- /client/components/Post.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import actions from '../store/actions/actions'; 4 | 5 | class Post extends Component { 6 | componentDidMount() { 7 | this.props.dispatch(actions.getUserData()) 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 | { 14 | this.props.user.login ?

{ this.props.user.login}

:

Loading...

15 | } 16 |
17 | ); 18 | } 19 | } 20 | 21 | function mapStateToProps( state ) { 22 | return { 23 | user : state.user 24 | }; 25 | } 26 | 27 | function loadData( store ) { 28 | return store.dispatch(actions.getUserData()); 29 | } 30 | 31 | export default { 32 | component: connect( mapStateToProps )( Post ), 33 | loadData 34 | }; 35 | -------------------------------------------------------------------------------- /client/components/Products.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import actions from '../store/actions/actions'; 4 | 5 | class Products extends Component { 6 | componentDidMount() { 7 | this.props.dispatch( actions.getProductsData() ); 8 | } 9 | 10 | render() { 11 | const { products } = this.props; 12 | console.warn( products ); 13 | return ( 14 |
15 | { products.length && ( 16 | products.map( item =>

{ item.name }

) 17 | ) } 18 |
19 | ); 20 | } 21 | } 22 | 23 | function mapStateToProps( state ) { 24 | return { 25 | products : state.products 26 | }; 27 | } 28 | 29 | function loadData( store ) { 30 | return store.dispatch(actions.getProductsData()); 31 | } 32 | 33 | export default { 34 | component: connect( mapStateToProps )( Products ), 35 | loadData 36 | }; 37 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {BrowserRouter,} from 'react-router-dom'; 4 | import { renderRoutes } from 'react-router-config'; 5 | import {Provider} from 'react-redux'; 6 | 7 | // Import Routes 8 | import routes from './routes'; 9 | 10 | // Import store 11 | import store from './store/store'; 12 | import Header from './components/Header'; 13 | 14 | const JSX =() => ( 15 | 16 | 17 | <> 18 |
19 |
20 | { 21 | renderRoutes(routes) 22 | } 23 |
24 | 25 | 26 | 27 | ) 28 | 29 | ReactDOM.hydrate(JSX(), document.getElementById('root')) 30 | -------------------------------------------------------------------------------- /client/routes.js: -------------------------------------------------------------------------------- 1 | import Home from "./components/Home"; 2 | import Post from "./components/Post"; 3 | import Products from "./components/Products"; 4 | 5 | export default [ 6 | { 7 | ...Home, 8 | path: '/', 9 | exact: true 10 | }, 11 | { 12 | ...Post, 13 | path: '/posts', 14 | exact: true, 15 | }, 16 | { 17 | ...Products, 18 | path: '/products', 19 | exact: true, 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /client/store/actions/actions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const actions = { 4 | getUserData : () => dispatch => { 5 | axios.get('https://api.github.com/users/imranhsayed') 6 | .then( userData => { 7 | dispatch({ 8 | type: "USER", 9 | user : userData.data, 10 | }) 11 | }) 12 | }, 13 | getProductsData : () => dispatch => { 14 | axios.get('/getProducts') 15 | .then( productData => { 16 | dispatch({ 17 | type: "PRODUCT", 18 | products : productData.data, 19 | }) 20 | }) 21 | } 22 | }; 23 | 24 | export default actions; 25 | -------------------------------------------------------------------------------- /client/store/reducers/rootReducer.js: -------------------------------------------------------------------------------- 1 | import actions from "../actions/actions"; 2 | 3 | const initState = { 4 | user: {}, 5 | products: {} 6 | }; 7 | 8 | function rootReducer( state = initState, action ) { 9 | switch( action.type ) { 10 | case "USER" : return { user : action.user }; 11 | case "PRODUCT" : return { products : action.products }; 12 | 13 | default: return state; 14 | } 15 | } 16 | 17 | export default rootReducer; 18 | -------------------------------------------------------------------------------- /client/store/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import rootReducer from './reducers/rootReducer'; 4 | 5 | const composeEnhancers = 6 | typeof window === 'object' && 7 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? 8 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 9 | // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize... 10 | }) : compose; 11 | 12 | const store = createStore( rootReducer, composeEnhancers( applyMiddleware( thunk ) ) ); 13 | 14 | export default store; 15 | -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | const keys = { 2 | consumerKey: 'xxxxxx', 3 | consumerSecret: 'xxxxx', 4 | }; 5 | 6 | export default keys; 7 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const keys = { 2 | consumerKey: 'ck_c798f49c41c39bb5f4e57fe406d0a4180cabc499', 3 | consumerSecret: 'cs_0f5f4521779f5972c26af2a0d2adfdebccd71400', 4 | }; 5 | 6 | export default keys; 7 | -------------------------------------------------------------------------------- /helper/template.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderToString } from "react-dom/server"; 3 | import { renderRoutes } from 'react-router-config'; 4 | import { StaticRouter } from 'react-router-dom'; 5 | import routes from '../client/routes'; 6 | import { Provider } from 'react-redux'; 7 | 8 | 9 | export function template( path, store ) { 10 | const jsx = renderToString( 11 | 12 | 13 |
{ renderRoutes( routes ) }
14 |
15 |
16 | ); 17 | return ` 18 | 19 | 20 |
${ jsx }
21 | 22 | 23 | 24 | ` 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-ssr", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "npm-run-all --parallel dev:*", 8 | "dev:server": "webpack --config webpack.server.js --watch", 9 | "dev:express": "nodemon dist/server.bundle.js", 10 | "dev:client": "webpack --config webpack.client.js --watch" 11 | }, 12 | "dependencies": { 13 | "@babel/core": "^7.3.4", 14 | "@babel/preset-env": "^7.3.4", 15 | "@babel/preset-react": "^7.0.0", 16 | "axios": "^0.18.0", 17 | "babel-loader": "^8.0.5", 18 | "express": "^4.16.4", 19 | "nodemon": "^1.18.10", 20 | "npm-run-all": "^4.1.5", 21 | "react": "^16.8.4", 22 | "react-dom": "^16.8.4", 23 | "react-redux": "^6.0.1", 24 | "react-router-config": "^4.4.0-beta.6", 25 | "react-router-dom": "^4.3.1", 26 | "redux": "^4.0.1", 27 | "redux-thunk": "^2.3.0", 28 | "webpack": "^4.29.6", 29 | "webpack-merge": "^4.2.1", 30 | "webpack-node-externals": "^1.7.2", 31 | "woocommerce-api": "^1.4.2" 32 | }, 33 | "devDependencies": { 34 | "webpack-cli": "^3.2.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { template } from '../helper/template'; 3 | import store from '../client/store/store'; 4 | import { matchRoutes } from 'react-router-config'; 5 | import routes from '../client/routes'; 6 | import WooCommerceAPI from 'woocommerce-api'; 7 | import config from '../config'; 8 | 9 | const app = express(); 10 | 11 | // Set path for static files 12 | app.use( express.static( 'dist' ) ); 13 | 14 | /** 15 | * Get All products 16 | */ 17 | app.get( '/getProducts', ( request, response ) => { 18 | var WooCommerce = new WooCommerceAPI({ 19 | url: 'https://www.orionhive.com/', 20 | consumerKey: config.consumerKey, 21 | consumerSecret: config.consumerSecret, 22 | wpAPI: true, 23 | version: 'wc/v3' 24 | }); 25 | 26 | WooCommerce.get('products', function(err, data, res) { 27 | response.json( JSON.parse(res) ); 28 | }); 29 | } ); 30 | 31 | app.get( '*', ( req, res ) => { 32 | 33 | // MATCHING ROUTES FOR FETCHING DATA 34 | const promises = matchRoutes( routes, req.path ).map( ( { route } ) => { 35 | return route.loadData ? route.loadData( store ) : null; 36 | }); 37 | 38 | Promise.all( promises ).then(() => { 39 | res.send( template( req.path, store ) ) 40 | }); 41 | }); 42 | 43 | 44 | app.listen( 3000, ( err ) => { 45 | console.log( `Server is running on http://localhost:3000` ); 46 | }); 47 | -------------------------------------------------------------------------------- /webpack.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: "development", 3 | module: { 4 | rules: [ 5 | { 6 | test: /\.js?$/, 7 | loader: "babel-loader", 8 | exclude: /node_modules/ 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /webpack.client.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const merge = require( 'webpack-merge' ); 3 | const baseConfig = require( './webpack.base' ); 4 | 5 | const config = { 6 | target: "web", 7 | entry: "./client/index.js", 8 | output: { 9 | filename: "client.bundle.js", 10 | path: path.resolve( __dirname, "dist" ) 11 | }, 12 | }; 13 | 14 | module.exports = merge( baseConfig, config ); 15 | -------------------------------------------------------------------------------- /webpack.server.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const merge = require( 'webpack-merge' ); 3 | const baseConfig = require( './webpack.base' ); 4 | const webpackNodeExternals = require( 'webpack-node-externals' ); 5 | 6 | const config = { 7 | target: "node", 8 | entry: "./server/index.js", 9 | output: { 10 | filename: "server.bundle.js", 11 | path: path.resolve( __dirname, "dist" ) 12 | }, 13 | externals: [ webpackNodeExternals() ] 14 | }; 15 | 16 | module.exports = merge( baseConfig, config ); 17 | --------------------------------------------------------------------------------