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

Added a not found view to product grid #2656

Merged
merged 15 commits into from
Aug 11, 2017
Merged
Show file tree
Hide file tree
Changes from 12 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
2 changes: 1 addition & 1 deletion imports/plugins/core/components/lib/composer/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default function compose(dataLoader, options = {}) {
const payload = { error, data };

if (!this._mounted) {
this.state = {
this.state = { // eslint-disable-line react/no-direct-mutation-state
...this.state,
...payload
};
Expand Down
23 changes: 2 additions & 21 deletions imports/plugins/core/ui/client/components/button/flatButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,20 @@ import { pure } from "recompose";
import { registerComponent } from "@reactioncommerce/reaction-components";
import Button from "./button.jsx";

const FlatButton = ({ icon, onIcon, ...otherProps }) => {
const FlatButton = (props) => {
const buttonClassName = classnames({
rui: true,
button: true
});

let iconClassName;
let onIconClassName;

if (icon) {
iconClassName = classnames({
[icon]: true
});
}

if (onIcon) {
onIconClassName = classnames({
[onIcon]: true
});
}

return (
<Button
className={buttonClassName}
icon={iconClassName}
onIcon={onIconClassName}
{...otherProps}
{...props}
/>
);
};

FlatButton.propTypes = Object.assign({}, Button.propTypes);

FlatButton.defaultProps = {
bezelStyle: "flat"
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ const IconButton = ({ icon, onIcon, ...otherProps }) => {
);
};

IconButton.propTypes = Object.assign({}, Button.propTypes);
IconButton.propTypes = {
...Button.propTypes,
icon: Button.propTypes.icon,
onIcon: Button.propTypes.onIcon
};

IconButton.defaultProps = {
bezelStyle: "solid"
Expand Down
1 change: 1 addition & 0 deletions imports/plugins/core/ui/client/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export { Overlay } from "./modal";
export * from "./tabs";
export { default as Select } from "./select/select.react";
export { default as ClickToCopy } from "./clickToCopy/clickToCopy";
export * from "./notFound";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as NotFound } from "./notFound";
100 changes: 100 additions & 0 deletions imports/plugins/core/ui/client/components/notFound/notFound.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from "react";
import PropTypes from "prop-types";
import classnames from "classnames";
import { Components, registerComponent } from "@reactioncommerce/reaction-components";
import { Icon } from "../icon";

/**
* React component for displaying a not-found view
* @param {Object} props React props
* @return {Node} React node containing not-found view
*/
const NotFound = (props) => {
// ClassName for base wrapper,
// If one is provied in props, the default is not used
const baseClassName = classnames({
"container-fluid-sm": typeof props.className === "undefined"
}, props.className);

// ClassName for content wrapper
// If one is provided in props, the default is not used
const contentClassName = classnames({
"empty-view-message": typeof props.contentClassName === "undefined"
}, props.contentClassName);

return (
<div className={baseClassName}>
<div className={contentClassName}>
{ props.icon &&
<Icon icon={props.icon} />
}

{ props.title &&
<Components.Translation defaultValue={props.title} i18nKey={props.i18nKeyTitle} />
}

{ props.message &&
<Components.Translation defaultValue={props.message} i18nKey={props.i18nKeyMessage} />
}

{props.children}
</div>
</div>
);
};

NotFound.propTypes = {
/**
* Children
* @type {Node}
*/
children: PropTypes.node,

/**
* Base wrapper className
* @summary String className or `classnames` compatible object for the base wrapper
* @type {String|Object}
*/
className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),

/**
* Content className
* @summary String className or `classnames` compatible object for the content wrapper
* @type {String|Object}
*/
contentClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),

/**
* i18n key for message
* @type {String}
*/
i18nKeyMessage: PropTypes.string,

/**
* i18n key for title
* @type {String}
*/
i18nKeyTitle: PropTypes.string,

/**
* Icon name
* @type {String}
*/
icon: PropTypes.string,

/**
* Message text
* @type {String}
*/
message: PropTypes.string,

/**
* Title
* @type {String}
*/
title: PropTypes.string
};

registerComponent("NotFound", NotFound);

export default NotFound;
119 changes: 102 additions & 17 deletions imports/plugins/included/product-variant/components/products.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,71 @@ import { Components } from "@reactioncommerce/reaction-components";
import { Reaction } from "/client/api";
import { getTagIds as getIds } from "/lib/selectors/tags";

/** Class representing the Products React component */
class Products extends Component {
static propTypes = {
/**
* Load more products callback
* @type {Function}
*/
loadMoreProducts: PropTypes.func,

/**
* Load products callback
* @type {Function}
*/
loadProducts: PropTypes.func,

/**
* Array of products
* @type {Array}
*/
products: PropTypes.array,

/**
* Products subscription
* @type {Object}
*/
productsSubscription: PropTypes.object,
ready: PropTypes.func

/**
* Ready state check helper
* @type {Function}
*/
ready: PropTypes.func,

/**
* Force show not-found view
* @type {Boolean}
*/
showNotFound: PropTypes.bool
};

/**
* Checks and returns a Boolean if the `protucts` array from props is not empty.
* @return {Boolean} Boolean value `true` if products are available, `false` otherwise.
*/
get hasProducts() {
return Array.isArray(this.props.products) && this.props.products.length > 0;
}

/**
* Handle load more button click
* @access protected
* @param {SyntheticEvent} event Synthetic event object
* @return {undefined}
*/
handleClick = (event) => {
if (this.props.loadProducts) {
this.props.loadProducts(event);
}
}

/**
* Render product grid
* @access protected
* @return {Node} React node containing the `ProductGrid` component.
*/
renderProductGrid() {
const products = this.props.products;

Expand All @@ -40,16 +90,24 @@ class Products extends Component {
);
}

/**
* Render loading component
* @access protected
* @return {Node} React node containing the `Loading` component.
*/
renderSpinner() {
if (this.props.productsSubscription.ready() === false) {
return (
<div className="spinner-container">
<div className="spinner" />
</div>
<Components.Loading />
);
}
}

/**
* Render load more button
* @access protected
* @return {Node|undefined} React node contianing a `laod more` button or undefined.
*/
renderLoadMoreProductsButton() {
if (this.props.loadMoreProducts()) {
return (
Expand All @@ -65,22 +123,49 @@ class Products extends Component {
}
}

render() {
if (this.props.ready()) {
return (
<div id="container-main">
{this.renderProductGrid()}
{this.renderLoadMoreProductsButton()}
{this.renderSpinner()}
</div>
);
}
/**
* Render the not found component
* @access protected
* @return {Node} React node contianing the `NotFound` component.
*/
renderNotFound() {
return (
<div className="spinner-container spinner-container-lg">
<div className="spinner" />
</div>
<Components.NotFound
i18nKeyTitle="productGrid.noProductsFound"
icon="fa fa-barcode"
title="No Products Found"
/>
);
}

/**
* Render component
* @access protected
* @return {Node} React node containing elements that make up the `Products` component.
*/
render() {
// Force show the not-found view.
if (this.props.showNotFound) {
return this.renderNotFound();
} else if (this.props.ready()) {
// Render products grid if products are available after subscription ready.
if (this.hasProducts) {
return (
<div id="container-main">
{this.renderProductGrid()}
{this.renderLoadMoreProductsButton()}
{this.renderSpinner()}
</div>
);
}

// Render not-found view if no products are available.
return this.renderNotFound();
}

// Render loading component by default if no condition above matches.
return this.renderSpinner();
}
}

export default Products;
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ const wrapComponent = (Comp) => (
static propTypes = {
canLoadMoreProducts: PropTypes.bool,
products: PropTypes.array,
productsSubscription: PropTypes.object
productsSubscription: PropTypes.object,
showNotFound: PropTypes.bool
};

constructor(props) {
Expand All @@ -61,6 +62,10 @@ const wrapComponent = (Comp) => (
}

ready = () => {
if (this.props.showNotFound === true) {
return false;
}

const isInitialLoad = this.state.initialLoad === true;
const isReady = this.props.productsSubscription.ready();

Expand All @@ -71,6 +76,7 @@ const wrapComponent = (Comp) => (
if (isReady) {
return true;
}

return false;
}

Expand All @@ -94,6 +100,7 @@ const wrapComponent = (Comp) => (
productsSubscription={this.props.productsSubscription}
loadMoreProducts={this.loadMoreProducts}
loadProducts={this.loadProducts}
showNotFound={this.props.showNotFound}
/>
);
}
Expand All @@ -116,8 +123,13 @@ function composer(props, onData) {

// if we get an invalid slug, don't return all products
if (!tag && slug) {
onData(null, {
showNotFound: true
});

return;
}

const currentTag = ReactionProduct.getTag();

const sort = {
Expand Down
3 changes: 3 additions & 0 deletions imports/plugins/included/product-variant/server/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"shortcut": {},
"dashboard": {},
"settings": {}
},
"productGrid": {
"noProductsFound": "No Products Found"
}
}
}
Expand Down