Skip to content
This repository has been archived by the owner on Dec 3, 2020. It is now read-only.

Commit

Permalink
Fix #33 and fix #35: Replace sidebar with pageAction panel.
Browse files Browse the repository at this point in the history
Removed the sidebar panel, and added a pageAction panel, which opens/closes on pageAction click.

Added a 'priceComparison' object that, similar to the 'product' object, is passed from 'background.js' to the pageAction panel ('./src/popup/index.jsx') via the panel's URL as a query string. This 'priceComparison' object is currently pulled from a stub method, 'getPriceComparisonData', which currently just returns a static object literal. Ultimately this method would call an external API and return the data we need to display in the panel.

TODO: Add CSS and top left-most icon.
  • Loading branch information
biancadanforth committed Jul 27, 2018
1 parent 00f8c5b commit 5dc1289
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 247 deletions.
46 changes: 28 additions & 18 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,16 @@
import {hasKeys} from 'commerce/utils';

const PRODUCT_KEYS = ['title', 'image', 'price'];
const SIDEBAR_URL = browser.extension.getURL('/sidebar.html');
const PRICE_COMPARISON_KEYS = ['altVendor', 'altPrice', 'faviconURL'];
const POPUP_URL = browser.extension.getURL('/popup.html');

/**
* Open the sidebar when the page action is clicked.
*/
browser.pageAction.onClicked.addListener(() => {
browser.sidebarAction.open();
});

/**
* Prep initial sidebar on location change for a given tab.
* Prep initial panel on location change for a given tab.
*/
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url && tab.status === 'loading') {
browser.sidebarAction.setPanel({
panel: SIDEBAR_URL,
browser.pageAction.setPopup({
popup: POPUP_URL,
tabId,
});
}
Expand All @@ -29,12 +23,13 @@ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
browser.runtime.onConnect.addListener((port) => {
port.onMessage.addListener((message) => {
if (message.type === 'product-data') {
// If this page contains a product, prep the sidebar and show the
// page action icon for opening the sidebar.
// If this page contains a product, prep the panel and show the
// page action icon for opening the panel.
const isProductPage = hasKeys(message.data, PRODUCT_KEYS);
if (isProductPage) {
browser.sidebarAction.setPanel({
panel: getPanelURL(message.data),
const priceComparison = getPriceComparisonData(port.sender.tab.url);
browser.pageAction.setPopup({
popup: getPanelURL(message.data, priceComparison),
tabId: port.sender.tab.id,
});
browser.pageAction.show(port.sender.tab.id);
Expand All @@ -47,12 +42,27 @@ browser.runtime.onConnect.addListener((port) => {
});

/**
* Generate the sidebar panel URL for a specific product.
* Generate the panel URL for a specific product.
*/
function getPanelURL(productData) {
const url = new URL(SIDEBAR_URL);
function getPanelURL(productData, priceComparison) {
const url = new URL(POPUP_URL);
for (const key of PRODUCT_KEYS) {
url.searchParams.set(key, productData[key]);
}
for (const key of PRICE_COMPARISON_KEYS) {
url.searchParams.set(key, priceComparison[key]);
}
return url.href;
}

/**
* This is a stub for a partner API that would fetch price comparison data.
*/
/* eslint-disable no-unused-vars */
function getPriceComparisonData(url) {
return {
altVendor: 'Walmart',
altPrice: '12.50',
faviconURL: 'https://www.walmart.com/favicon.ico',
};
}
8 changes: 2 additions & 6 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,15 @@
},
"page_action": {
"default_icon": "icon.svg",
"default_title": "Show Shopping Sidebar"
"default_title": "Show Shopping Panel",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["product_info.bundle.js"]
}
],
"sidebar_action": {
"default_icon": "icon.svg",
"default_title": "Commerce",
"default_panel": "sidebar.html"
},
"permissions": [
"<all_urls>",
"tabs"
Expand Down
4 changes: 2 additions & 2 deletions src/sidebar.html → src/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<title></title>
</head>
<body>
<div id="sidebar"></div>
<script src="sidebar.bundle.js"></script>
<div id="popup"></div>
<script src="popup.bundle.js"></script>
</body>
</html>
Empty file added src/popup/components/Button.css
Empty file.
43 changes: 43 additions & 0 deletions src/popup/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import autobind from 'autobind-decorator';
import pt from 'prop-types';
import React from 'react';

import 'commerce/popup/components/Button.css';

/**
* An expandable section in an Accordion container. Must be used as a
* direct child of an Accordion.
*/
@autobind
export default class Button extends React.Component {
static propTypes = {
/** Text displayed in on the button */
label: pt.string.isRequired,
/** If there are multiple buttons, true if this is the primary button */
primary: pt.bool,
onClick: pt.func,

}

static defaultProps = {
primary: false,
onClick() {},
}

handleClick() {
this.props.onClick(this.props.primary);
}

render() {
const {label, primary} = this.props;
return (
<button type="button" className={`button ${primary ? 'primary' : ''}`} onClick={this.handleClick}>
<span className="button-label">{label}</span>
</button>
);
}
}
6 changes: 0 additions & 6 deletions src/sidebar/index.css → src/popup/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,3 @@ body {
height: 100%;
width: 100%;
}

#sidebar,
.sidebar-container {
width: 100%;
height: 100%;
}
90 changes: 90 additions & 0 deletions src/popup/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import React from 'react';
import ReactDOM from 'react-dom';
import pt from 'prop-types';

import Button from 'commerce/popup/components/Button';
import {hasKeys} from 'commerce/utils';

import 'commerce/popup/index.css';

const PRODUCT_KEYS = ['title', 'image', 'price'];
const PRICE_COMPARISON_KEYS = ['altVendor', 'altPrice', 'faviconURL'];

class Popup extends React.Component {
static propTypes = {
product: pt.shape({
title: pt.string,
image: pt.string,
price: pt.string,
}),
priceComparison: pt.shape({
altVendor: pt.string,
altPrice: pt.string,
faviconURL: pt.string,
}),
}

static defaultProps = {
product: undefined,
priceComparison: undefined,
}

componentDidCatch() {
// TODO: Show friendly message when errors happen.
}

render() {
const {price} = this.props.product;
const {altVendor, altPrice, faviconURL} = this.props.priceComparison;
// remove '$', '-'' and starting/ending ' ' from the string and
// split string if given a range of prices (e.g. Crate and Barrel), and
// convert to a number with two decimal places
const cleanPrice = parseInt(price
.replace(/\$|-/g, '')
.trim()
.split(/\s/)[0], 10);
const savings = (cleanPrice - altPrice) > 0 ? (cleanPrice - altPrice).toFixed(2) : 0;
return (
<div>
<h1>You could save <span>${savings}</span> buying this elsewhere.</h1>
<h2>We found this same product available for less on another site:</h2>
<div>
<img alt={`${altVendor} favicon`} src={faviconURL} />
<span>{altVendor}</span>
<span>${altPrice}</span>
</div>
<Button className="button" label="No Thanks" />
<Button className="button" primary label="Show Me" />
</div>
);
}
}

// Pull in product data from query parameters
const url = new URL(window.location);
const product = {
title: url.searchParams.get('title'),
image: url.searchParams.get('image'),
price: url.searchParams.get('price'),
};
const isValidProduct = PRODUCT_KEYS.map(key => product[key]).every(val => val);

// Pull in price comparison data from query parameters
const priceComparison = {
altVendor: url.searchParams.get('altVendor'),
altPrice: url.searchParams.get('altPrice'),
faviconURL: url.searchParams.get('faviconURL'),
};
const isBetterPrice = hasKeys(priceComparison, PRICE_COMPARISON_KEYS);

ReactDOM.render(
<Popup
product={isValidProduct ? product : undefined}
priceComparison={isBetterPrice ? priceComparison : undefined}
/>,
document.getElementById('popup'),
);
30 changes: 0 additions & 30 deletions src/sidebar/components/Accordion.css

This file was deleted.

Loading

0 comments on commit 5dc1289

Please sign in to comment.