Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Indicator to notify of pending product changes not yet published to catalog #4383

Merged
merged 15 commits into from
Jun 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 85 additions & 6 deletions imports/plugins/core/catalog/client/components/publishControls.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import classnames from "classnames";
import { Meteor } from "meteor/meteor";
import { ReactiveVar } from "meteor/reactive-var";
import { Components } from "@reactioncommerce/reaction-components";
import {
Button,
Expand All @@ -10,7 +13,7 @@ import {
import { Translatable } from "/imports/plugins/core/ui/client/providers";

/** TMP **/
import { Reaction } from "/client/api";
import { i18next, Reaction } from "/client/api";

class PublishControls extends Component {
static propTypes = {
Expand All @@ -37,14 +40,28 @@ class PublishControls extends Component {
constructor(props) {
super(props);

this.state = {
showDiffs: false
};
this.currentProductHash = new ReactiveVar([]);

this.handleToggleShowChanges = this.handleToggleShowChanges.bind(this);
this.handlePublishClick = this.handlePublishClick.bind(this);
}

state = {
isHashUpdating: false,
showDiffs: false
};

componentWillReceiveProps() {
this.setState({
isHashUpdating: false
});
}

componentDidUpdate() {
// Re-calculate hash after publishing
this.renderHashCalculation();
}

handleToggleShowChanges() {
this.setState({
showDiffs: !this.state.showDiffs
Expand All @@ -54,6 +71,10 @@ class PublishControls extends Component {
handlePublishClick() {
if (this.props.onPublishClick) {
this.props.onPublishClick(this.props.revisions);

this.setState({
isHashUpdating: true
});
}
}

Expand Down Expand Up @@ -200,13 +221,13 @@ class PublishControls extends Component {

return (
<div className="hidden-xs">
{this.renderChangesNotification()}
<Button
bezelStyle="outline"
bezelStyle="solid"
disabled={isDisabled}
label="Publish"
onClick={this.handlePublishClick}
status="success"
tooltip={"This product has changes that need to be published before they are visible to your customers."}
i18nKeyLabel="productDetailEdit.publish"
{...buttonProps}
/>
Expand Down Expand Up @@ -304,6 +325,64 @@ class PublishControls extends Component {
);
}

renderHashCalculation = () => {
const productDocument = this.props && this.props.documents && this.props.documents[0];

if (productDocument) {
Meteor.call("products/getpublishedProductHash", productDocument._id, (err, result) => {
if (err) {
Alerts.toast(i18next.t("admin.catalogCalculateHashError", { err: err.reason }), "error");
}
if (result) {
this.currentProductHash.set(result);
}
});
}
return;
}

renderChangesNotification = () => {
const publishedProductHash = (this.props && this.props.documents && this.props.documents[0] && this.props.documents[0].publishedProductHash) || null;
const { isHashUpdating } = this.state;

// Calculate hash to compare
this.renderHashCalculation();
const currentProductHash = this.currentProductHash.get();

const hashIndicator = classnames({
"rui": true,
"hash-icon": true,
"fa-stack": true,
"fa-lg": true,
"hash-icon-visible": publishedProductHash !== currentProductHash,
"hash-icon-hidden": publishedProductHash === currentProductHash
});

const primaryIcon = classnames({
"fa": true,
"fa-stack-2x": true,
"fa-circle": isHashUpdating,
"fa-circle-o": !isHashUpdating
});

const secondaryIcon = classnames({
"fa": true,
"fa-stack-1x": true,
"fa-refresh": isHashUpdating,
"fa-circle": !isHashUpdating,
"fa-spin": isHashUpdating
});

// We don't use the `<Icon>` component here as we do not have layered
// icons built in to the existing component
return (
<span className={hashIndicator} style={{ fontSize: 11, position: "absolute", top: "2px", right: "4px" }}>
<i className={primaryIcon} style={{ color: "#ffffff" }} />
<i className={secondaryIcon} style={{ fontSize: "1.5em", color: "#f4c43c" }} />
</span>
);
}

render() {
return (
<Components.ToolbarGroup lastChild={true}>
Expand Down
1 change: 1 addition & 0 deletions imports/plugins/core/catalog/server/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"reaction-catalog": {
"admin": {
"catalogProductPublishSuccess": "Product published to catalog",
"catalogCalculateHashError": "There was an error trying to calculate this products hash.",
"shortcut": {
"catalogLabel": "Catalog",
"catalogTitle": "Catalog"
Expand Down
19 changes: 19 additions & 0 deletions imports/plugins/core/catalog/server/methods/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Hooks, Logger, Reaction } from "/server/api";
import { MediaRecords, Products, Tags } from "/lib/collections";
import { Media } from "/imports/plugins/core/files/server";
import rawCollections from "/imports/collections/rawCollections";
import { createProductHash } from "../no-meteor/mutations/hashProduct";
import getProductPriceRange from "../no-meteor/utils/getProductPriceRange";
import getVariants from "../no-meteor/utils/getVariants";
import hasChildVariant from "../no-meteor/utils/hasChildVariant";
Expand Down Expand Up @@ -1576,5 +1577,23 @@ Meteor.methods({

// if collection updated we return new `isVisible` state
return res === 1 && !product.isVisible;
},

/**
* @name products/getpublishedProductHash
* @memberof Methods/Products
* @method
* @summary hashes product information for comparison purposes
* @param {String} productId - the product _id of the product to hash
* @return {String} hash of product
*/
"products/getpublishedProductHash"(productId) {
check(productId, String);

const product = Products.findOne({ _id: productId });

const productHash = createProductHash(product, rawCollections);

return productHash;
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import createCatalogProduct from "../utils/createCatalogProduct";
* @param {Object} collections - Raw mongo collections
* @return {String} product hash
*/
async function createProductHash(productToConvert, collections) {
export async function createProductHash(productToConvert, collections) {
const product = await createCatalogProduct(productToConvert, collections);

const hashableFields = {
Expand Down
12 changes: 12 additions & 0 deletions imports/plugins/included/default-theme/client/styles/toolbar.less
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ html.rtl .rui.toolbar.toolbar-vertical {
display: flex !important;
}
}

.rui.hash-icon {
transition: opacity 200ms linear;
}

.rui.hash-icon.hash-icon-visible {
opacity: 1.0;
}

.rui.hash-icon.hash-icon-hidden {
opacity: 0.0;
}