├── src ├── Landing.css ├── ProductSummary.css ├── book-mock.jpg ├── gig-mock.jpg ├── Navigation.css ├── App.test.js ├── App.js ├── ReactEshop.css ├── MissingRoute.js ├── ProductDetail.css ├── reFirebase.js ├── index.js ├── products.json ├── logo.svg ├── products-firebase.json ├── Landing.js ├── Navigation.js ├── ProductDetail.js └── ProductSummary.js ├── public ├── favicon.ico └── index.html ├── .flowconfig ├── .gitignore ├── package.json └── README.md /src/Landing.css: -------------------------------------------------------------------------------- 1 | .Landing__featured { 2 | margin-top: 50px; 3 | } 4 | -------------------------------------------------------------------------------- /src/ProductSummary.css: -------------------------------------------------------------------------------- 1 | .ProductSummary__price { 2 | color: seagreen; 3 | } 4 | -------------------------------------------------------------------------------- /src/book-mock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manavsehgal/react-eshop/HEAD/src/book-mock.jpg -------------------------------------------------------------------------------- /src/gig-mock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manavsehgal/react-eshop/HEAD/src/gig-mock.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manavsehgal/react-eshop/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/fbjs/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [options] 9 | -------------------------------------------------------------------------------- /src/Navigation.css: -------------------------------------------------------------------------------- 1 | .Navigation__container { 2 | margin-bottom: -30px; 3 | } 4 | 5 | .Navigation__promo, .Navigation__search-spacer { 6 | margin-top: 5px; 7 | } 8 | 9 | .Navigation__promo { 10 | text-align: center; 11 | } 12 | -------------------------------------------------------------------------------- /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 | }); 9 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Navigation from './Navigation.js'; 3 | 4 | export default class App extends Component { 5 | render() { 6 | return ( 7 |
8 | 9 | {this.props.children} 10 |
11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ReactEshop.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | 7 | .ReactEshop__nav-spacer { 8 | min-height: 50px; 9 | } 10 | 11 | .ReactEshop__price { 12 | color: seagreen; 13 | } 14 | 15 | .ReactEshop__market-price { 16 | text-decoration: line-through; 17 | color: slategray; 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | 17 | # firebase 18 | .firebaserc 19 | database.rules.json 20 | firebase.json 21 | -------------------------------------------------------------------------------- /src/MissingRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid } from 'react-bootstrap'; 3 | 4 | const MissingRoute = () => ( 5 |
6 |
7 | 8 |

Oops! We Could Not Find That...

9 |

10 | Please use top menu to navigate elsewhere. 11 |

12 |
13 |
14 | ); 15 | 16 | export default MissingRoute; 17 | -------------------------------------------------------------------------------- /src/ProductDetail.css: -------------------------------------------------------------------------------- 1 | .ProductDetail__summary { 2 | font-size: 1.4em; 3 | } 4 | 5 | .ProductDetail__description { 6 | margin-top: 10px; 7 | font-size: 1.2em; 8 | } 9 | 10 | .ProductDetail__price { 11 | font-size: 2em; 12 | line-height: 2em; 13 | } 14 | 15 | .ProductDetail__about-price { 16 | color: slategray; 17 | font-size: 0.8em; 18 | } 19 | 20 | .ProductDetail__book-detail, 21 | .ProductDetail__gig-detail { 22 | font-size: 1.2em; 23 | padding-top: 5px; 24 | padding-bottom: 10px; 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-eshop", 3 | "version": "0.1.0", 4 | "private": true, 5 | "eslintConfig": { 6 | "extends": "react-app" 7 | }, 8 | "devDependencies": { 9 | "react-scripts": "0.5.1" 10 | }, 11 | "dependencies": { 12 | "bootstrap": "^3.3.7", 13 | "firebase": "^3.4.1", 14 | "react": "^15.3.2", 15 | "react-bootstrap": "^0.30.3", 16 | "react-dom": "^15.3.2", 17 | "react-router": "^2.8.1" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test --env=jsdom", 23 | "eject": "react-scripts eject" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Eshop 2 | 3 | [Book](https://leanpub.com/reacteshop) | [Website](https://reacteshop.com) 4 | 5 | Chapter by chapter code along branches for React Eshop book. 6 | 7 | ## Download and Run Complete App 8 | 9 | You can clone and run the final app we complete at the end of React Eshop book. 10 | 11 | ``` 12 | git clone https://github.com/manavsehgal/react-eshop.git 13 | cd react-eshop 14 | npm install 15 | npm start 16 | ``` 17 | 18 | ## Code Along Chapter by Chapter 19 | 20 | You can clone the source for this book, change to app directory, checkout just the code for a chapter, install and start the app to launch the 21 | local version in your default browser. 22 | 23 | ``` 24 | git clone https://github.com/manavsehgal/react-eshop.git 25 | cd react-eshop 26 | git checkout -b c01 origin/c01-easy-start-react 27 | npm install 28 | npm start 29 | ``` 30 | -------------------------------------------------------------------------------- /src/reFirebase.js: -------------------------------------------------------------------------------- 1 | const firebase = require('firebase/app'); 2 | require('firebase/auth'); 3 | require('firebase/database'); 4 | require('firebase/storage'); 5 | 6 | const config = { 7 | apiKey: "AIzaSyCekbJrfuOoErE6bF5auczgYnq5Hs32anQ", 8 | authDomain: "reacteshop.firebaseapp.com", 9 | databaseURL: "https://reacteshop.firebaseio.com", 10 | storageBucket: "reacteshop.appspot.com", 11 | messagingSenderId: "1016974227748" 12 | }; 13 | 14 | // As reFirebase is included within multiple components, 15 | // check to see if initializeApp is already called, 16 | // firebase.apps array contains all initialized apps 17 | if(firebase.apps.length === 0) { 18 | firebase.initializeApp(config); 19 | } 20 | 21 | const reFirebase = firebase; 22 | 23 | export const reDatabase = firebase.database(); 24 | export const reStorage = firebase.storage(); 25 | export default reFirebase; 26 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './ReactEshop.css'; 5 | import 'bootstrap/dist/css/bootstrap.css'; 6 | import 'bootstrap/dist/css/bootstrap-theme.css'; 7 | import MissingRoute from './MissingRoute.js'; 8 | import ProductDetail from './ProductDetail.js'; 9 | import Landing from './Landing.js'; 10 | import { Route, Router, IndexRoute, browserHistory } from 'react-router'; 11 | 12 | const routes = 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ; 22 | 23 | ReactDOM.render( 24 | routes, 25 | document.getElementById('root') 26 | ); 27 | -------------------------------------------------------------------------------- /src/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { "name": "React Eshop", "category": "Book", "price": 9.99, 3 | "market_price": 19.99, 4 | "summary": "Easily reusable Eshop in React, ES6, and Facebook.", 5 | "link": "https://leanpub.com/reacteshop", "referral": true}, 6 | "2": { "name": "Setup Webpack React", "category": "Gig", "price": 25, "featured": true, 7 | "market_price": 45, 8 | "duration": "1 day", "deliverable": "GitHub repo", 9 | "description": "We will setup React Webpack development environment for you. You will receive access to GitHub repo we create. You can also share your private repo for us to update.", 10 | "summary": "Development environment setup using React and Webpack."}, 11 | "3": { "name": "React Speed Coding", "category": "Book", "price": 9.99, 12 | "market_price": 19.99, 13 | "pages": 327, "reader_level": "Beginner to Intermediate", 14 | "description": "Speed up your React development workflow using Webpack. Learn to build a custom UI library in React, ES6, Flexbox, and PostCSS. Create single page app using Redux actions, reducers, and store. Apply new ES6 features to make your React code more reliable. Apply Behavior-Driven Development using Mocha, Chai, and Enzyme unit tests. Master the complete component design workflow with several strategies for reusable, reliable, and rapid coding in React.", 15 | "summary": "Develop custom UI library in React, Flexbox, and PostCSS."}, 16 | "4": { "name": "Custom React Stack", "category": "Gig", "price": 95, 17 | "duration": "2-3 days", "deliverable": "Word document", 18 | "summary": "Custom React tech stack based on your project."}, 19 | "5": { "name": "Custom React Component", "category": "Gig", "price": 45, 20 | "duration": "2-3 days", "deliverable": "Component code", 21 | "summary": "Custom React component development for your project."} 22 | } 23 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | <% 17 | var title = 'React Eshop | Easily reusable Eshop in React'; 18 | var description = 'Reusable Eshop app in React, complete with a shopping cart, product catalog, and search.'; 19 | %> 20 | <%= title%> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/products-firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { "name": "React Eshop", "category": "Book", "price": 9.99, 3 | "market_price": 19.99, 4 | "thumb" : "https://firebasestorage.googleapis.com/v0/b/reacteshop.appspot.com/o/products%2Freacteshop-cover-leanpub.jpg?alt=media&token=7d86211c-a570-4475-ac97-5030d18da4c6", 5 | "summary": "Easily reusable Eshop in React, ES6, and Facebook.", 6 | "link": "https://leanpub.com/reacteshop", "referral": true}, 7 | "2": { "name": "Setup Webpack React", "category": "Gig", "price": 25, "featured": true, 8 | "market_price": 45, 9 | "thumb" : "https://firebasestorage.googleapis.com/v0/b/reacteshop.appspot.com/o/products%2Freactspeed-projects-codingsale.jpg?alt=media&token=e7dbc507-0a55-4f84-8f26-87bb1d2fb59d", 10 | "duration": "1 day", "deliverable": "GitHub repo", 11 | "description": "We will setup React Webpack development environment for you. You will receive access to GitHub repo we create. You can also share your private repo for us to update.", 12 | "summary": "Development environment setup using React and Webpack."}, 13 | "3": { "name": "React Speed Coding", "category": "Book", "price": 9.99, 14 | "market_price": 19.99, 15 | "thumb" : "https://firebasestorage.googleapis.com/v0/b/reacteshop.appspot.com/o/products%2Freactspeed-cover-leanpub.jpg?alt=media&token=ad21b55d-24e0-41f9-8b9d-da30e2e54a10", 16 | "pages": 327, "reader_level": "Beginner to Intermediate", 17 | "description": "Speed up your React development workflow using Webpack. Learn to build a custom UI library in React, ES6, Flexbox, and PostCSS. Create single page app using Redux actions, reducers, and store. Apply new ES6 features to make your React code more reliable. Apply Behavior-Driven Development using Mocha, Chai, and Enzyme unit tests. Master the complete component design workflow with several strategies for reusable, reliable, and rapid coding in React.", 18 | "summary": "Develop custom UI library in React, Flexbox, and PostCSS."}, 19 | "4": { "name": "Custom React Stack", "category": "Gig", "price": 95, 20 | "thumb" : "https://firebasestorage.googleapis.com/v0/b/reacteshop.appspot.com/o/products%2Fgig-mock.jpg?alt=media&token=1b7f2c85-b557-4458-bcb4-f1664642df64", 21 | "duration": "2-3 days", "deliverable": "Word document", 22 | "summary": "Custom React tech stack based on your project."}, 23 | "5": { "name": "Custom React Component", "category": "Gig", "price": 45, 24 | "thumb" : "https://firebasestorage.googleapis.com/v0/b/reacteshop.appspot.com/o/products%2Fgig-mock.jpg?alt=media&token=1b7f2c85-b557-4458-bcb4-f1664642df64", 25 | "duration": "2-3 days", "deliverable": "Component code", 26 | "summary": "Custom React component development for your project."} 27 | } 28 | -------------------------------------------------------------------------------- /src/Landing.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ProductSummary from './ProductSummary'; 3 | import { Grid, Row, Col } from 'react-bootstrap'; 4 | import productsData from './products.json'; 5 | import { reDatabase } from './reFirebase'; 6 | import './Landing.css'; 7 | 8 | export default class Landing extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | productsData: JSON.parse(JSON.stringify(productsData)), 13 | firebase: false 14 | } 15 | } 16 | componentDidMount() { 17 | const getProducts = (snap) => { 18 | this.setState({ 19 | productsData: snap.val(), 20 | firebase: true 21 | }); 22 | }; 23 | 24 | // Swap ref().once with ref().on for realtime catalog updates 25 | // Getting data once if obviously more performant, as app is 26 | // not polling for changes 27 | 28 | // reDatabase.ref('products').on('value', getProducts); 29 | reDatabase.ref('products').once('value').then(getProducts); 30 | } 31 | render() { 32 | const products = this.state.productsData; 33 | const productFeatured = Object.keys(products).map(key => { 34 | return (products[key].featured 35 | ? 48 | : null 49 | ); 50 | }); 51 | const productCatalog = Object.keys(products).map(key => { 52 | return (!products[key].featured 53 | ? 54 | 66 | 67 | : null 68 | ); 69 | }); 70 | return ( 71 |
72 |
73 | {productFeatured} 74 |
75 | 76 | {productCatalog} 77 | 78 |
79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Navigation.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Grid, Form, FormControl, Navbar, Glyphicon, 3 | Nav, NavItem, Well, Row, Col, Button } from 'react-bootstrap'; 4 | import { Link } from 'react-router'; 5 | import './Navigation.css'; 6 | import reFirebase from './reFirebase'; 7 | 8 | export default class Navigation extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { logout: false }; 12 | this.login = this.login.bind(this); 13 | this.logout = this.logout.bind(this); 14 | } 15 | componentDidMount() { 16 | reFirebase.auth().onAuthStateChanged((user) => { 17 | if (user) { 18 | // User is signed in. 19 | this.setState({ logout: true }); 20 | } else { 21 | // No user is signed in. 22 | this.setState({ logout: false }); 23 | } 24 | }); 25 | } 26 | login() { 27 | const provider = new reFirebase.auth.GoogleAuthProvider(); 28 | 29 | reFirebase.auth().signInWithPopup(provider).then((result) => { 30 | // This gives you a Google Access Token. You can use it to access the Google API. 31 | const token = result.credential.accessToken; 32 | // The signed-in user info. 33 | const user = result.user; 34 | console.log("User signin " + user + " with token " + token ); 35 | // ... 36 | // User is signed in. 37 | this.setState({ logout: true }); 38 | }).catch(function(error) { 39 | // Handle Errors here. 40 | const errorCode = error.code; 41 | const errorMessage = error.message; 42 | // The email of the user's account used. 43 | const email = error.email; 44 | // The firebase.auth.AuthCredential type that was used. 45 | const credential = error.credential; 46 | // ... 47 | console.log("Err " + errorCode + " msg " + errorMessage 48 | + " email " + email + " cred " + credential); 49 | }); 50 | } 51 | logout() { 52 | reFirebase.auth().signOut().then(() => { 53 | this.setState({ logout: false }); 54 | }, function(error) { 55 | // An error happened. 56 | }); 57 | } 58 | render() { 59 | return ( 60 |
61 | 62 | 63 | 64 | React Eshop 65 | 66 | 67 | 68 | 69 | 82 | 83 | 84 |
85 | 86 | 87 | 88 | 89 |
90 |
91 | 92 | 93 | 94 | 95 |
96 | 97 | {this.state.logout 98 | ? 100 | : } 102 | 103 |    104 | {this.state.logout 105 | ? 106 | Welcome back 107 |   108 | {reFirebase.auth().currentUser.email} 109 | 110 | : Please login to add products to your cart} 111 |
112 | 113 | 114 | 115 | 116 |
117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/ProductDetail.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import './ProductDetail.css'; 3 | import { Grid, Row, Col, Thumbnail, Glyphicon, Badge, Button } 4 | from 'react-bootstrap'; 5 | import bookThumb from './book-mock.jpg'; 6 | import gigThumb from './gig-mock.jpg'; 7 | import productsData from './products.json'; 8 | const products = JSON.parse(JSON.stringify(productsData)); 9 | import { reDatabase } from './reFirebase'; 10 | 11 | export default class ProductDetail extends Component { 12 | static propTypes = { 13 | params: PropTypes.object.isRequired 14 | } 15 | constructor(props) { 16 | super(props); 17 | this.state = { inCart: 0, checkoutTally: 0, product: null}; 18 | this.getProduct = this.getProduct.bind(this); 19 | } 20 | componentWillMount() { 21 | const slug = this.props.params.slug; 22 | const id = slug.substring(slug.indexOf('--') + 2); 23 | this.setState({ product: products[id] }); 24 | } 25 | componentDidMount() { 26 | const getProducts = (snap) => { 27 | const productsFirebase = snap.val(); 28 | const slug = this.props.params.slug; 29 | const id = slug.substring(slug.indexOf('--') + 2); 30 | this.setState({ 31 | product: productsFirebase[id], 32 | firebase: true 33 | }); 34 | }; 35 | reDatabase.ref('products').once('value').then(getProducts); 36 | } 37 | // Get product button event handler 38 | getProduct() { 39 | this.setState({ 40 | inCart: this.state.inCart + 1, 41 | checkoutTally: Math.round(( 42 | this.state.checkoutTally + 43 | this.state.product.price) * 100) / 100 44 | }); 45 | } 46 | // View cart button event handler 47 | viewCart() { 48 | alert("View Cart"); 49 | } 50 | // Checkout button event handler 51 | checkout() { 52 | alert("Checkout"); 53 | } 54 | render() { 55 | let detail = 56 | 57 |

We could not find that product.

58 |

Try searching or using the top navigation 59 | to find what you are looking for.

60 |
; 61 | 62 | if (this.state.product) { 63 | const mockThumb = 64 | this.state.product.category === 'Book' ? bookThumb : gigThumb; 65 | const productThumb = 66 | 71 | 72 | const price = 73 |
74 | Price:  75 | 76 | ${this.state.product.price} 77 | 78 | {this.state.product.market_price 79 | ? 80 |  reduced from ${this.state.product.market_price} 81 | 82 | : null} 83 |
; 84 | 85 | const bookDetail = 86 |
87 | 88 | 89 | Pages: {this.state.product.pages} 90 | 91 | 92 |  |   93 | Reader Level:  94 | {this.state.product.reader_level} 95 | 96 |
; 97 | 98 | const gigDetail = 99 |
100 | 101 | Duration:  102 | {this.state.product.duration} 103 | 104 | 105 |  | Deliverable:  106 | {this.state.product.deliverable} 107 | 108 |
; 109 | 110 | // Display inCart quantity if not zero, otherwise display icon 111 | const callToActionContent = this.state.inCart 112 | ? {this.state.inCart} 113 | : ; 114 | 115 | // Display get product button 116 | const getProductButton = 117 | ; 123 | 124 | // Display checkout button conditionally 125 | let checkoutButton = this.state.checkoutTally 126 | ? 127 | 133 |   134 | 140 | 141 | : null; 142 | 143 | // Display product detail 144 | detail = 145 | 146 | 147 | 148 | {productThumb} 149 | 150 | 151 |

152 | {this.state.product.name} 153 | {this.state.product.category} 154 |

155 | {price} 156 |

157 | {this.state.product.summary} 158 |

159 | {this.state.product.category === 160 | 'Book' ? bookDetail : null} 161 | {this.state.product.category === 162 | 'Gig' ? gigDetail : null} 163 | {getProductButton} 164 |   165 | {checkoutButton} 166 |

167 | {this.state.product.description} 168 |

169 | 170 |
171 |
; 172 | } 173 | 174 | return( 175 |
176 |
177 | {detail} 178 |
179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/ProductSummary.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Grid, Row, Col, Well, Thumbnail, Button, Glyphicon, Badge } 3 | from 'react-bootstrap'; 4 | import bookThumb from './book-mock.jpg'; 5 | import gigThumb from './gig-mock.jpg'; 6 | import reFirebase from './reFirebase'; 7 | 8 | export default class ProductSummary extends Component { 9 | static propTypes = { 10 | name: PropTypes.string.isRequired, 11 | summary: PropTypes.string.isRequired, 12 | category: PropTypes.string.isRequired, 13 | price: PropTypes.number.isRequired, 14 | display: PropTypes.string.isRequired, 15 | id: PropTypes.string.isRequired, 16 | marketPrice: PropTypes.number, 17 | referral: PropTypes.bool, 18 | thumb: PropTypes.string, 19 | link: PropTypes.string, 20 | } 21 | static defaultProps = { 22 | marketPrice: 0, 23 | link: '', 24 | referral: false 25 | } 26 | static contextTypes = { 27 | router: PropTypes.object.isRequired 28 | } 29 | constructor(props) { 30 | super(props); 31 | 32 | this.state = { inCart: 0, checkoutTally: 0 } 33 | this.getProduct = this.getProduct.bind(this); 34 | this.productDetail = this.productDetail.bind(this); 35 | this.checkout = this.checkout.bind(this); 36 | this.viewCart = this.viewCart.bind(this); 37 | } 38 | slugify(text) { 39 | return text.toString().toLowerCase().trim() 40 | // Replace & with 'and' 41 | .replace(/&/g, '-and-') 42 | // Replace spaces, non-word characters and dashes with a single dash (-) 43 | .replace(/[\s\W-]+/g, '-') 44 | } 45 | // Product detail button event handler 46 | productDetail() { 47 | if (this.props.referral) { 48 | window.open(this.props.link); 49 | } else { 50 | const slug = 51 | `${this.slugify(this.props.name)}--${this.props.id}`; 52 | this.context.router.push(`detail/${slug}`); 53 | } 54 | } 55 | // Get Product button event handler 56 | getProduct() { 57 | if (this.props.referral) { 58 | window.open(this.props.link); 59 | } else { 60 | const user = reFirebase.auth().currentUser; 61 | if (user) { 62 | // User is signed in. 63 | this.setState({ 64 | inCart: this.state.inCart + 1, 65 | checkoutTally: Math.round(( 66 | this.state.checkoutTally + 67 | this.props.price) * 100) / 100 68 | }); 69 | } else { 70 | const provider = new reFirebase.auth.GoogleAuthProvider(); 71 | // Firebase callback's this context is not this component 72 | reFirebase.auth().signInWithPopup(provider).then((result) => { 73 | // This gives you a Google Access Token. You can use it to access the Google API. 74 | const token = result.credential.accessToken; 75 | // The signed-in user info. 76 | const user = result.user; 77 | console.log("User signin " + user + " with token " + token ); 78 | // ... 79 | // User is signed in. 80 | this.setState({ 81 | inCart: this.state.inCart + 1, 82 | checkoutTally: Math.round(( 83 | this.state.checkoutTally + 84 | this.props.price) * 100) / 100 85 | }); 86 | }).catch(function(error) { 87 | // Handle Errors here. 88 | const errorCode = error.code; 89 | const errorMessage = error.message; 90 | // The email of the user's account used. 91 | const email = error.email; 92 | // The firebase.auth.AuthCredential type that was used. 93 | const credential = error.credential; 94 | // ... 95 | console.log("Err " + errorCode + " msg " + errorMessage 96 | + " email " + email + " cred " + credential); 97 | }); 98 | } 99 | } 100 | } 101 | viewCart() { 102 | alert("View Cart"); 103 | } 104 | checkout() { 105 | alert("Checkout"); 106 | } 107 | render() { 108 | // Different icon depending on Referral or Direct distribution 109 | const callToActionIcon = this.props.referral 110 | ? 111 | : ; 112 | 113 | // Display inCart quantity if not zero, otherwise display icon 114 | const callToActionContent = this.state.inCart 115 | ? {this.state.inCart} 116 | : callToActionIcon; 117 | 118 | // Different color depending on Referral or Direct distribution 119 | const callToActionStyle = this.props.referral 120 | ? 'primary' : 'success'; 121 | 122 | // Render this for Button bsSize 123 | let buttonSize = ''; 124 | 125 | switch(this.props.display) { 126 | case 'splash': buttonSize = 'large'; break; 127 | case 'card': buttonSize = 'small'; break; 128 | // Handle future display targets 129 | default: buttonSize = 'small'; 130 | } 131 | 132 | // Render this for Get Product button 133 | const getProductButton = 134 | 141 | 142 | // Render this for Product detail button 143 | let productDetailButton = 144 | ; 150 | 151 | // Render this for Checkout button 152 | let checkoutButton = 153 | 154 | 160 |   161 | 167 | 168 | 169 | // No product detail required for referral products 170 | productDetailButton = this.props.referral 171 | ? null 172 | : productDetailButton; 173 | 174 | // Render Checkout button conditionally 175 | productDetailButton = this.state.checkoutTally > 0 176 | ? checkoutButton 177 | : productDetailButton; 178 | 179 | // Render this for call-to-action buttons 180 | const buttons =

{getProductButton} {productDetailButton}

; 181 | 182 | const mockThumb = this.props.category === 'Book' ? bookThumb : gigThumb; 183 | 184 | // Render this for product thumb 185 | let productThumb = 186 | ; 191 | 192 | // Render this for product summary - splash or card (default) style 193 | let renderProductSummary = ''; 194 | 195 | if (this.props.display === 'splash') { 196 | renderProductSummary = 197 | 198 | 199 | 200 | {productThumb} 201 | 202 | 203 |

{this.props.name} {this.props.category}

204 |

{this.props.summary}

205 |

206 | ${this.props.price} 207 |   208 | {this.props.marketPrice 209 | ? 210 | ${this.props.marketPrice} 211 | 212 | : null} 213 |

214 | {buttons} 215 | 216 |
217 |
; 218 | }; 219 | if (this.props.display === 'card') { 220 | renderProductSummary = 221 | 222 | {productThumb} 223 |

{this.props.name} {this.props.category}

224 |

225 | Price: 226 | ${this.props.price} 227 |   228 | {this.props.marketPrice 229 | ? 230 | ${this.props.marketPrice} 231 | 232 | : null} 233 |

234 |

{this.props.summary}

235 | {buttons} 236 |
; 237 | }; 238 | return (renderProductSummary); 239 | } 240 | } 241 | --------------------------------------------------------------------------------