-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #380 from reactioncommerce/feat-kieckhafer-invento…
…ryStatusComponent feat: add new inventoryStatus component
- Loading branch information
Showing
18 changed files
with
436 additions
and
3 deletions.
There are no files selected for viewing
90 changes: 90 additions & 0 deletions
90
package/src/components/InventoryStatus/v1/InventoryStatus.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import React, { Component } from "react"; | ||
import PropTypes from "prop-types"; | ||
import styled from "styled-components"; | ||
import { withComponents } from "@reactioncommerce/components-context"; | ||
import { addTypographyStyles, CustomPropTypes } from "../../../utils"; | ||
import { STATUS_LABELS, inventoryStatus } from "./utils"; | ||
|
||
const SoldOutSpan = styled.div` | ||
${addTypographyStyles("StockWarning", "labelText")} | ||
`; | ||
|
||
const DefaultSpan = styled.div` | ||
${addTypographyStyles("", "labelText")} | ||
`; | ||
|
||
class InventoryStatus extends Component { | ||
static propTypes = { | ||
/** | ||
* You can provide a `className` prop that will be applied to the outermost DOM element | ||
* rendered by this component. We do not recommend using this for styling purposes, but | ||
* it can be useful as a selector in some situations. | ||
*/ | ||
className: PropTypes.string, | ||
/** | ||
* If you've set up a components context using | ||
* [@reactioncommerce/components-context](https://github.com/reactioncommerce/components-context) | ||
* (recommended), then this prop will come from there automatically. If you have not | ||
* set up a components context or you want to override one of the components in a | ||
* single spot, you can pass in the components prop directly. | ||
*/ | ||
components: PropTypes.shape({ | ||
/** | ||
* Pass either the Reaction StockWarning component or your own component that | ||
* accepts compatible props. | ||
*/ | ||
StockWarning: CustomPropTypes.component.isRequired | ||
}).isRequired, | ||
/** | ||
* The product, whose properties determine which badge(s) to display | ||
*/ | ||
product: PropTypes.shape({ | ||
inventoryAvailableToSell: PropTypes.number, | ||
isBackorder: PropTypes.bool, | ||
isLowQuantity: PropTypes.bool, | ||
isSoldOut: PropTypes.bool | ||
}), | ||
/** | ||
* Labels to use for the various badges | ||
*/ | ||
statusLabels: PropTypes.shape({ | ||
BACKORDER: PropTypes.string, | ||
LOW_QUANTITY: PropTypes.string, | ||
SOLD_OUT: PropTypes.string | ||
}) | ||
}; | ||
|
||
static defaultProps = { | ||
statusLabels: STATUS_LABELS | ||
}; | ||
|
||
render() { | ||
const { className, components, product, statusLabels } = this.props; | ||
const { StockWarning } = components || {}; | ||
|
||
const status = inventoryStatus(product, statusLabels); | ||
|
||
if (!status) return null; | ||
|
||
if (status.type && status.type === "LOW_QUANTITY") { | ||
return ( | ||
<StockWarning | ||
inventoryQuantity={product.inventoryAvailableToSell} | ||
isLowInventoryQuantity={product.isLowQuantity} | ||
/> | ||
); | ||
} | ||
|
||
if (status.type && status.type === "SOLD_OUT") { | ||
return ( | ||
<SoldOutSpan className={className}>{status.label}</SoldOutSpan> | ||
); | ||
} | ||
|
||
return ( | ||
<DefaultSpan className={className}>{status.label}</DefaultSpan> | ||
); | ||
} | ||
} | ||
|
||
export default withComponents(InventoryStatus); |
62 changes: 62 additions & 0 deletions
62
package/src/components/InventoryStatus/v1/InventoryStatus.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
### Overview | ||
The `InventoryStatus` displays a low inventory warning when the `isLowQuantity` prop is true. | ||
|
||
### Usage | ||
|
||
An inventory warning will be rendered when the `isLowQuantity` prop is `true`, and does not render when a product has a normal inventory level. | ||
|
||
#### Backorder | ||
```jsx | ||
const productData = { | ||
isBackorder: true, | ||
isLowQuantity: true, | ||
isSoldOut: true, | ||
inventoryAvailableToSell: 0 | ||
}; | ||
|
||
<InventoryStatus product={productData} /> | ||
``` | ||
|
||
#### Low inventory | ||
```jsx | ||
const productData = { | ||
isBackorder: false, | ||
isLowQuantity: true, | ||
isSoldOut: false, | ||
inventoryAvailableToSell: 4 | ||
}; | ||
|
||
<InventoryStatus product={productData} /> | ||
``` | ||
|
||
#### Regular inventory | ||
```jsx | ||
const productData = { | ||
isBackorder: false, | ||
isLowQuantity: false, | ||
isSoldOut: false, | ||
inventoryAvailableToSell: 4 | ||
}; | ||
|
||
<InventoryStatus product={productData} /> | ||
``` | ||
|
||
#### Sold out | ||
```jsx | ||
const productData = { | ||
isBackorder: false, | ||
isLowQuantity: true, | ||
isSoldOut: true, | ||
inventoryAvailableToSell: 0 | ||
}; | ||
|
||
<InventoryStatus product={productData} /> | ||
``` | ||
|
||
### Theme | ||
|
||
See [Theming Components](./#!/Theming%20Components). | ||
|
||
#### Typography | ||
|
||
- The text uses `labelText` style with `rui_components.InventoryStatus` override |
74 changes: 74 additions & 0 deletions
74
package/src/components/InventoryStatus/v1/InventoryStatus.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from "react"; | ||
import renderer from "react-test-renderer"; | ||
import checkPropTypes from "check-prop-types"; | ||
import mockComponents from "../../../tests/mockComponents"; | ||
import InventoryStatus from "./InventoryStatus"; | ||
|
||
test("Displays error warning about required props", () => { | ||
const errorMessage = checkPropTypes(InventoryStatus.propTypes, {}); | ||
expect(errorMessage).toMatchSnapshot(); | ||
}); | ||
|
||
test("Renders backorder notification when inventory is sold out and backorder is allowed", () => { | ||
const productData = { | ||
isBackorder: true, | ||
isLowQuantity: true, | ||
isSoldOut: true, | ||
inventoryAvailableToSell: 0 | ||
}; | ||
|
||
const component = renderer.create(( | ||
<InventoryStatus components={mockComponents} product={productData} /> | ||
)); | ||
|
||
const tree = component.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
test("Renders low inventory notification when inventory is lower than threshold", () => { | ||
const productData = { | ||
isBackorder: false, | ||
isLowQuantity: true, | ||
isSoldOut: false, | ||
inventoryAvailableToSell: 6 | ||
}; | ||
|
||
const component = renderer.create(( | ||
<InventoryStatus components={mockComponents} product={productData} /> | ||
)); | ||
|
||
const tree = component.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
test("Renders nothing when inventory is ready to be sold", () => { | ||
const productData = { | ||
isBackorder: false, | ||
isLowQuantity: false, | ||
isSoldOut: false, | ||
inventoryAvailableToSell: 4 | ||
}; | ||
|
||
const component = renderer.create(( | ||
<InventoryStatus components={mockComponents} product={productData} /> | ||
)); | ||
|
||
const tree = component.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
test("Renders sold out notification when inventory is sold out and backorder is not allowed", () => { | ||
const productData = { | ||
isBackorder: false, | ||
isLowQuantity: true, | ||
isSoldOut: true, | ||
inventoryAvailableToSell: 0 | ||
}; | ||
|
||
const component = renderer.create(( | ||
<InventoryStatus components={mockComponents} product={productData} /> | ||
)); | ||
|
||
const tree = component.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); |
53 changes: 53 additions & 0 deletions
53
package/src/components/InventoryStatus/v1/__snapshots__/InventoryStatus.test.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Displays error warning about required props 1`] = `undefined`; | ||
|
||
exports[`Renders backorder notification when inventory is sold out and backorder is allowed 1`] = ` | ||
.c0 { | ||
-webkit-font-smoothing: antialiased; | ||
color: #505558; | ||
font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,sans-serif; | ||
font-size: 14px; | ||
font-style: normal; | ||
font-stretch: normal; | ||
font-weight: 400; | ||
-webkit-letter-spacing: .02em; | ||
-moz-letter-spacing: .02em; | ||
-ms-letter-spacing: .02em; | ||
letter-spacing: .02em; | ||
line-height: 1.25; | ||
} | ||
<div | ||
className="c0" | ||
> | ||
Backordered - ships when available | ||
</div> | ||
`; | ||
|
||
exports[`Renders low inventory notification when inventory is lower than threshold 1`] = `"StockWarning({\\"inventoryQuantity\\":6,\\"isLowInventoryQuantity\\":true})"`; | ||
|
||
exports[`Renders nothing when inventory is ready to be sold 1`] = `null`; | ||
|
||
exports[`Renders sold out notification when inventory is sold out and backorder is not allowed 1`] = ` | ||
.c0 { | ||
-webkit-font-smoothing: antialiased; | ||
color: #cd3f4c; | ||
font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,sans-serif; | ||
font-size: 14px; | ||
font-style: normal; | ||
font-stretch: normal; | ||
font-weight: 400; | ||
-webkit-letter-spacing: .02em; | ||
-moz-letter-spacing: .02em; | ||
-ms-letter-spacing: .02em; | ||
letter-spacing: .02em; | ||
line-height: 1.25; | ||
} | ||
<div | ||
className="c0" | ||
> | ||
Out of stock | ||
</div> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./InventoryStatus"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export { default as STATUS_TYPES } from "./statusTypes"; | ||
export { default as STATUS_LABELS } from "./statusLabels"; | ||
export { default as inventoryStatus } from "./inventoryStatus"; | ||
|
||
export { default as isProductLowQuantity } from "./isProductLowQuantity"; |
22 changes: 22 additions & 0 deletions
22
package/src/components/InventoryStatus/v1/utils/inventoryStatus.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { STATUS_TYPES } from "./"; | ||
|
||
/** | ||
* Determines a product's badge status | ||
* | ||
* @param {Object} product - The product | ||
* @param {Object} statusLabels - Labels to use for badges | ||
* @returns {Object} - The computed product status | ||
*/ | ||
export default function inventoryStatus(product, statusLabels) { | ||
let status; | ||
|
||
if (product.isSoldOut && product.isBackorder) { | ||
status = { type: STATUS_TYPES.BACKORDER, label: statusLabels.BACKORDER }; | ||
} else if (product.isSoldOut && !product.isBackorder) { | ||
status = { type: STATUS_TYPES.SOLD_OUT, label: statusLabels.SOLD_OUT }; | ||
} else if (product.isLowQuantity && !product.isSoldOut) { | ||
status = { type: STATUS_TYPES.LOW_QUANTITY, label: statusLabels.LOW_QUANTITY }; | ||
} | ||
|
||
return status; | ||
} |
29 changes: 29 additions & 0 deletions
29
package/src/components/InventoryStatus/v1/utils/inventoryStatus.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import inventoryStatus from "./inventoryStatus"; | ||
import STATUS_TYPES from "./statusTypes"; | ||
import STATUS_LABELS from "./statusLabels"; | ||
|
||
const backorderProduct = { isSoldOut: true, isBackorder: true }; | ||
const soldOutProduct = { isSoldOut: true, isBackorder: false }; | ||
const isLowQuantity = { isLowQuantity: true }; | ||
|
||
|
||
test("inventoryStatus util should return `backorder` status", () => { | ||
const callFunction = inventoryStatus(backorderProduct, STATUS_LABELS); | ||
|
||
expect(typeof inventoryStatus).toBe("function"); | ||
expect(callFunction).toEqual({ type: STATUS_TYPES.BACKORDER, label: "Backordered - ships when available" }); | ||
}); | ||
|
||
test("inventoryStatus util should return `sold out` status", () => { | ||
const callFunction = inventoryStatus(soldOutProduct, STATUS_LABELS); | ||
|
||
expect(typeof inventoryStatus).toBe("function"); | ||
expect(callFunction).toEqual({ type: STATUS_TYPES.SOLD_OUT, label: "Out of stock" }); | ||
}); | ||
|
||
test("inventoryStatus util should return `low inventory` status", () => { | ||
const callFunction = inventoryStatus(isLowQuantity, STATUS_LABELS); | ||
|
||
expect(typeof inventoryStatus).toBe("function"); | ||
expect(callFunction).toEqual({ type: STATUS_TYPES.LOW_QUANTITY, label: "Low Inventory" }); | ||
}); |
11 changes: 11 additions & 0 deletions
11
package/src/components/InventoryStatus/v1/utils/isProductBestseller.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** | ||
* Determines if a product is a best seller. | ||
* TODO: this is a placeholder, as we don't have "Best Seller" at this moment | ||
* https://github.com/reactioncommerce/reaction-next-starterkit/issues/130 | ||
* | ||
* @param {Object} product - The product | ||
* @returns {Boolean} - Indicates whether the product is a best seller | ||
*/ | ||
export default function isProductBestseller(product) { | ||
return product.isBestseller || false; | ||
} |
18 changes: 18 additions & 0 deletions
18
package/src/components/InventoryStatus/v1/utils/isProductBestseller.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import isProductBestseller from "./isProductBestseller"; | ||
|
||
const isBestseller = { isBestseller: true }; | ||
const isNotBestseller = { isBestseller: false }; | ||
|
||
test("isProductBestseller should return false", () => { | ||
const callFunction = isProductBestseller(isNotBestseller); | ||
|
||
expect(typeof isProductBestseller).toBe("function"); | ||
expect(callFunction).toEqual(false); | ||
}); | ||
|
||
test("isProductBestseller should return true", () => { | ||
const callFunction = isProductBestseller(isBestseller); | ||
|
||
expect(typeof isProductBestseller).toBe("function"); | ||
expect(callFunction).toEqual(true); | ||
}); |
9 changes: 9 additions & 0 deletions
9
package/src/components/InventoryStatus/v1/utils/isProductLowQuantity.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/** | ||
* Determines if a product has low inventory. | ||
* | ||
* @param {Object} product - The product | ||
* @returns {Boolean} - Indicates whether the product has low inventory | ||
*/ | ||
export default function isProductLowQuantity(product) { | ||
return product.isLowQuantity && !product.isSoldOut; | ||
} |
Oops, something went wrong.