Skip to content

Commit

Permalink
Add Snipcart w/ lazy loading
Browse files Browse the repository at this point in the history
  • Loading branch information
JeanSebTr committed Jun 27, 2018
1 parent 2bbea7c commit 6eff61c
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 18 deletions.
2 changes: 1 addition & 1 deletion content/guides/survival/flesh-eating.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion content/guides/survival/index.md
@@ -1,6 +1,6 @@
---
title: "Preparing for the apocalypse"
cover: "./arm.svg"
cover: "./flesh-eating.svg"
date: "01/01/2017"
products:
-
Expand Down
18 changes: 9 additions & 9 deletions data/SiteConfig.js
Expand Up @@ -6,9 +6,10 @@ module.exports = {
siteUrl: "https://vagr9k.github.io", // Domain of your website without pathPrefix.
pathPrefix: "/", // Prefixes all links. For cases when deployed to example.github.io/gatsby-material-starter/.
fixedFooter: false, // Whether the footer component is fixed, i.e. always visible
siteDescription: "A GatsbyJS stater with Material design in mind.", // Website description used for RSS feeds/meta description tag.
siteDescription: "A demo Progressive Web App with GatsbyJS and Snipcart",
siteRss: "/rss.xml", // Path to the RSS file.
postDefaultCategoryID: "Tech", // Default category for posts.
snipcartApiKey: "MzMxN2Y0ODMtOWNhMy00YzUzLWFiNTYtZjMwZTRkZDcxYzM4",
userName: "Material User", // Username to display in the author segment.
userTwitter: "", // Optionally renders "Follow Me" in the UserInfo segment.
userLocation: "North Pole, Earth", // User location to display in the author segment.
Expand All @@ -17,21 +18,20 @@ module.exports = {
"Yeah, I like animals better than people sometimes... Especially dogs. Dogs are the best. Every time you come home, they act like they haven't seen you in a year. And the good thing about dogs... is they got different dogs for different people.", // User description to display in the author segment.
// Links to social profiles/projects you want to display in the author segment/navigation bar.
userLinks: [
{
label: "Your Cart",
iconClassName: "fa fa-shopping-cart"
},
{
label: "GitHub",
url: "https://github.com/Vagr9K/gatsby-material-starter",
url: "https://github.com/snipcart/",
iconClassName: "fa fa-github"
},
{
label: "Twitter",
url: "https://twitter.com/Vagr9K",
url: "https://twitter.com/Snipcart",
iconClassName: "fa fa-twitter"
},
{
label: "Email",
url: "mailto:vagr9k@gmail.com",
iconClassName: "fa fa-envelope"
}
],
copyright: "Copyright © 2017. Material User" // Copyright string for the footer of the website and RSS feed.
copyright: "Made with <3 by Snipcart" // Copyright string for the footer of the website and RSS feed.
};
1 change: 1 addition & 0 deletions plugins/offline-with-worker/gatsby-browser.js
@@ -0,0 +1 @@
exports.registerServiceWorker = () => true;
40 changes: 40 additions & 0 deletions plugins/offline-with-worker/gatsby-node.js
@@ -0,0 +1,40 @@
const _ = require(`lodash`);
const fs = require(`fs`);

const offlinePlugin = require('gatsby-plugin-offline/gatsby-node.js');
const offlinePluginPostBuild = offlinePlugin.onPostBuild;

function modifyWebpackConfig({ config, stage }, options) {
if (stage === "build-javascript" && options.workerScript) {
config.merge((conf) => {
conf.entry['worker'] = options.workerScript;
return conf;
});
}
};

let s
const readStats = () => {
if (s) {
return s
} else {
s = JSON.parse(
fs.readFileSync(`${process.cwd()}/public/stats.json`, `utf-8`)
)
return s
}
}

const getAssetsForChunk = (chunk) =>
_.flatten(readStats().assetsByChunkName[chunk])

const onPostBuild = (args, pluginOptions) => {
const workerFile = getAssetsForChunk('worker').filter((file) => file.substr(-3) === '.js').pop();
console.log(workerFile);
return offlinePluginPostBuild(args, {
...pluginOptions,
importScripts: [workerFile]
});
};

module.exports = {...offlinePlugin, modifyWebpackConfig, onPostBuild};
11 changes: 11 additions & 0 deletions plugins/offline-with-worker/package.json
@@ -0,0 +1,11 @@
{
"name": "offline-with-worker",
"version": "1.0.0",
"description": "",
"main": "gatsby-node.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
23 changes: 19 additions & 4 deletions src/components/ProductListing/ProductListing.jsx
Expand Up @@ -6,13 +6,25 @@ import CardText from "react-md/lib/Cards/CardText";
import "./ProductListing.scss";

class ProductListing extends React.Component {
componentDidMount() {
this.url = window.location.href;
this.host = window.location.protocol + "//" + window.location.host;
}
render() {
return (
<Card className="md-grid md-cell md-cell--12 products-list">
<CardText>
<ul>
{this.props.products.map(product => (
<li>
{this.props.products.map(product => {
var def = {
id: product.sku,
price: product.price,
name: product.name,
image: (this.host || "") + product.image.publicURL,
url: this.url,
};
return (
<li key={product.sku}>
<figure>
<img src={product.image.publicURL} />
</figure>
Expand All @@ -21,12 +33,15 @@ class ProductListing extends React.Component {
<Button raised secondary className="snipcart-add-item"
data-item-id={product.sku}
data-item-price={product.price}
data-item-name={product.name}>
data-item-name={product.name}
data-item-image={def.image}
data-item-url={this.url}
data-snip-def={JSON.stringify(def)}>
Buy for {product.price}$
</Button>
</p>
</li>
))}
)})}
</ul>
</CardText>
</Card>
Expand Down
133 changes: 133 additions & 0 deletions src/components/Snipcart/Snipcart.jsx
@@ -0,0 +1,133 @@
import React, { Component } from "react";

class Snipcart extends Component {
componentDidMount() {
this.productsQueue = [];
this.isSnipcartReady = false;
this.cssLoading = false;
this.cssLoaded = false;
this.scriptLoading = null;
this.eventSubscribed = false;

this.updateScripts = this.updateScripts.bind(this);
this.handleProductClick = this.handleProductClick.bind(this);
this.handleItemAdding = this.handleItemAdding.bind(this);
this.snipcartReady = this.snipcartReady.bind(this);

window.addEventListener('online', this.updateScripts);
document.body.addEventListener('click', this.handleProductClick);
document.addEventListener('snipcart.ready', this.snipcartReady);

this.updateScripts();
}
componentWillUnmount() {
window.removeEventListener('online', this.updateScripts);
document.body.removeEventListener('click', this.handleProductClick);
document.removeEventListener('snipcart.ready', this.snipcartReady);
}
handleProductClick(e) {
if(!e.target.classList.contains("snipcart-add-item") || this.isSnipcartLoaded()) {
return;
}

var item = JSON.parse(e.target.getAttribute('data-snip-def'));
console.log("Queuing clicked item", item);
this.productsQueue.push(item);
}
handleItemAdding(ev, item) {
if(window.navigator.onLine) {
return;
}
ev.preventDefault();
console.log("Queuing item from snip event", item);
this.productsQueue.push(item);
}
snipcartReady() {
console.log("Snipcart finished loading");
window.Snipcart.subscribe('item.adding', this.handleItemAdding);

this.isSnipcartReady = true;

this.dequeueProducts();
}
updateScripts() {
if(!window.navigator.onLine) {
return;
}

if(!this.cssLoaded && !this.cssLoading) {
this.loadSnipCss();
}

var jQueryLoaded = !!(typeof window.$ == "function" && window.$.fn && window.$.fn.jquery);
if(!jQueryLoaded) {
return this.loadjQuery().then(this.updateScripts);
}

if(!this.isSnipcartLoaded()) {
return this.loadSnipJs().then(this.updateScripts);
}

if(this.isSnipcartReady) {
this.dequeueProducts();
}
}
dequeueProducts() {
window.Snipcart.api.cart.start().then(() => {
console.log("Dequeueing products", this.productsQueue);
if(this.productsQueue.length > 0) {
window.Snipcart.api.items.add(this.productsQueue);
this.productsQueue = [];
}
});
}
isSnipcartLoaded() {
return !!(window.Snipcart);
}
loadjQuery() {
return this.addElem('script', {
async: true,
src: "https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js",
});
}
loadSnipJs() {
return this.addElem('script', {
async: true,
id: "snipcart",
src: "https://cdn.snipcart.com/scripts/2.0/snipcart.js",
"data-api-key": this.props.apiKey,
});
}
loadSnipCss() {
this.cssLoading = true;
return this.addElem('link', {
async: true,
type: "text/css",
rel: "stylesheet",
href: "https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css",
})
.then(() => this.cssLoaded = true)
.finally(() => this.cssLoading = false);
}
addElem(tag, attrs) {
return new Promise((resolve, reject) => {
var el = document.createElement(tag);
el.onload = resolve;
el.onerror = reject;

var keys = Object.keys(attrs);

for(var i=0; i<keys.length; i++) {
var key = keys[i];
el.setAttribute(key, attrs[key]);
}

document.head.appendChild(el);
});
}
render() {
return null;
}
}

export default Snipcart;
1 change: 1 addition & 0 deletions src/components/UserLinks/UserLinks.jsx
Expand Up @@ -13,6 +13,7 @@ class UserLinks extends Component {
secondary
key={link.label}
iconClassName={link.iconClassName}
className={link.className || ""}
href={link.url}
>
{labeled ? link.label : ""}
Expand Down
8 changes: 5 additions & 3 deletions src/layouts/index.jsx
Expand Up @@ -5,6 +5,7 @@ import Navigation from "../components/Navigation/Navigation";
import config from "../../data/SiteConfig";
import "./index.scss";
import "./global.scss";
import Snipcart from "../components/Snipcart/Snipcart";

export default class MainLayout extends React.Component {
getLocalTitle() {
Expand Down Expand Up @@ -43,15 +44,16 @@ export default class MainLayout extends React.Component {
}
render() {
const { children } = this.props;
return (
<Navigation config={config} LocalTitle={this.getLocalTitle()}>
return [
<Snipcart key="snipcartComponent" apiKey={config.snipcartApiKey} />,
<Navigation key="navComponent" config={config} LocalTitle={this.getLocalTitle()}>
<div>
<Helmet>
<meta name="description" content={config.siteDescription} />
</Helmet>
{children()}
</div>
</Navigation>
);
];
}
}
1 change: 1 addition & 0 deletions src/worker.js
@@ -0,0 +1 @@
export default {};

0 comments on commit 6eff61c

Please sign in to comment.