Skip to content

Commit

Permalink
Merge pull request #12 from clouda-inc/feature-sku-comparison
Browse files Browse the repository at this point in the history
Limit comparison bucket size, Enhance comparison context to add array of items at once
  • Loading branch information
diyoda authored Jan 11, 2021
2 parents d0fa523 + 1cffb34 commit 103ad4c
Show file tree
Hide file tree
Showing 15 changed files with 182 additions and 22 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Implemented loading behaviour for products comparison page.

### Changed
- Limit comparison bucket size and given it as app configuration parameter
- Enhance product comparison context to add array of items at once
- Show notifications when comparison bucket is full

## [0.5.0] - 2020-11-19

Expand Down
12 changes: 12 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ In any desired template, such as the `store.search`, add the `product-comparison
| :------------: | :-----------: | :----------------------------: | :--------: |
| `skuSpecificationsToHide` | `[string]` | List of SKU specification fields that should be hidden on the Product Comparison page. The desired SKU specification fields must be separated by comma. | `undefined` |


### Step 6 - Change the comparison bucket size

This is optional configuration, you can change the maximum number of items in comparison bucket by giving fixed number in app configuration.
Default value is 10 items, when you exceed maximum limit you will get a notification.

#### Steps

1. Go to `/admin/apps`, you will find `Product Comparison` app under `installed` apps
2. Click on settings
3. Put the maximum comparison bucket size in your application

## Customization

In order to apply CSS customizations in this and other blocks, follow the instructions given in the recipe on [Using CSS Handles for store customization](https://vtex.io/docs/recipes/style/using-css-handles-for-store-customization).
Expand Down
14 changes: 13 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@
"vtex.slider-layout": "0.x",
"vtex.list-context": "0.x",
"vtex.product-list-context": "0.x",
"vtex.flex-layout": "0.x"
"vtex.flex-layout": "0.x",
"vtex.apps-graphql": "2.x"
},
"settingsSchema": {
"title": "Product comparison",
"type": "object",
"properties": {
"maxNumberOfItemsToCompare": {
"title": "Comparison bucket upper limit",
"description": "Maximum number of items to compare. Default value is 10",
"type": "number"
}
}
},
"$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema"
}
5 changes: 3 additions & 2 deletions messages/context.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
"store/product-comparison.main-page.sort-by": "Sort by",
"store/product-comparison.main-page.order-added": "Order added",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.title": "Hide Product Specification Group",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)"
}
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)",
"store/product-comparison.product-selector.upper-limit-exceeded": "Can't add this item to compare, bucket size exceeded"
}
5 changes: 3 additions & 2 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
"store/product-comparison.main-page.sort-by": "Sort by",
"store/product-comparison.main-page.order-added": "Order added",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.title": "Hide Product Specification Group",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)"
}
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)",
"store/product-comparison.product-selector.upper-limit-exceeded": "Can't add this item to compare, bucket size exceeded"
}
5 changes: 3 additions & 2 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
"store/product-comparison.main-page.sort-by": "Sort by",
"store/product-comparison.main-page.order-added": "Order added",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.title": "Hide Product Specification Group",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)"
}
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)",
"store/product-comparison.product-selector.upper-limit-exceeded": "Can't add this item to compare, bucket size exceeded"
}
5 changes: 3 additions & 2 deletions messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
"store/product-comparison.main-page.sort-by": "Sort by",
"store/product-comparison.main-page.order-added": "Order added",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.title": "Hide Product Specification Group",
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)"
}
"admin/editor.comparison-grid.product-specification-groups-to-be-removed.description": "Names of product specification groups which needs to be hidden in comparison page (separate them by comma `,`)",
"store/product-comparison.product-selector.upper-limit-exceeded": "Can't add this item to compare, bucket size exceeded"
}
16 changes: 9 additions & 7 deletions react/ComparisonPage.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React, { ReactChildren, ReactChild } from 'react'
import { pathOr, isEmpty } from 'ramda'
import React, { ReactChild, ReactChildren } from 'react'
import { isEmpty, pathOr } from 'ramda'
import { useCssHandles } from 'vtex.css-handles'
import {
Button,
Layout,
PageHeader,
PageBlock,
Button,
PageHeader,
Spinner,
withToast,
// Dropdown,
} from 'vtex.styleguide'
import ComparisonContext from './ProductComparisonContext'
import { InjectedIntlProps, injectIntl, defineMessages } from 'react-intl'
import { defineMessages, InjectedIntlProps, injectIntl } from 'react-intl'
import './global.css'

const CSS_HANDLES = ['pageContainer', 'sortBy', 'removeAllItemsButtonWrapper']
Expand Down Expand Up @@ -80,7 +80,9 @@ const ComparisonPage = ({ children, intl, showToast }: Props) => {
}

return isEmpty(comparisonProducts) ? (
<div />
<div className={'mw3 center'}>
<Spinner />
</div>
) : (
<div className={`${cssHandles.pageContainer} mw9 center`}>
<Layout
Expand Down
87 changes: 85 additions & 2 deletions react/ProductComparisonContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import React, {
useEffect,
} from 'react'
import { pathOr, reject, propEq, allPass } from 'ramda'
import { useQuery } from 'react-apollo'
import AppSettings from './queries/AppSettings.graphql'

export interface State {
isDrawerCollapsed: boolean
showDifferences: boolean
products: ProductToCompare[]
maxNumberOfItemsToCompare: number
}

interface SetShowDifferences {
Expand All @@ -24,6 +27,11 @@ interface AddAll {
args: { products: ProductToCompare[] }
}

interface AddMultiple {
type: 'ADD_MULTIPLE'
args: { products: ProductToCompare[] }
}

interface RemoveAll {
type: 'REMOVE_ALL'
}
Expand All @@ -43,28 +51,68 @@ interface IsDrawerCollapsed {
args: { isDrawerCollapsed: boolean }
}

interface SetMaxLimit {
type: 'SET_MAX_LIMIT'
args: { maxLimit: number }
}

type ReducerActions =
| AddAll
| AddMultiple
| Add
| RemoveAll
| Remove
| SetShowDifferences
| IsDrawerCollapsed
| SetMaxLimit

export type Dispatch = (action: ReducerActions) => void

const listReducer = (state: State, action: ReducerActions): State => {
switch (action.type) {
case 'ADD_ALL': {
const products = pathOr([], ['args', 'products'], action)
const productsToCompare = [...state.products, ...products].slice(
0,
state.maxNumberOfItemsToCompare
)
return {
...state,
products: productsToCompare,
}
}
case 'ADD_MULTIPLE': {
const products = pathOr([], ['args', 'products'], action)

const productsToAdd = products.filter(
(product: ProductToCompare) =>
state.products.filter(
(existing: ProductToCompare) =>
existing.productId === product.productId &&
existing.skuId === product.skuId
).length === 0
)

const newProductList = [...state.products, ...productsToAdd].slice(
0,
state.maxNumberOfItemsToCompare
)

localStorage.setItem(
'PRODUCTS_TO_COMPARE',
JSON.stringify(newProductList)
)
return {
...state,
products: [...state.products, ...products],
products: newProductList,
}
}
case 'ADD': {
const { product } = action.args
const newProductList = [...state.products, product]
const newProductList = [...state.products, product].slice(
0,
state.maxNumberOfItemsToCompare
)
localStorage.setItem(
'PRODUCTS_TO_COMPARE',
JSON.stringify(newProductList)
Expand Down Expand Up @@ -105,16 +153,25 @@ const listReducer = (state: State, action: ReducerActions): State => {
isDrawerCollapsed: action.args.isDrawerCollapsed,
}
}
case 'SET_MAX_LIMIT': {
return {
...state,
maxNumberOfItemsToCompare: action.args.maxLimit,
}
}
default: {
throw new Error(`Unhandled action type on product-list-context`)
}
}
}

const MAX_ITEMS_TO_COMPARE = 10

const DEFAULT_STATE: State = {
isDrawerCollapsed: false,
showDifferences: false,
products: [] as ProductToCompare[],
maxNumberOfItemsToCompare: MAX_ITEMS_TO_COMPARE,
}

const ComparisonContext = createContext<State>(DEFAULT_STATE)
Expand All @@ -126,6 +183,7 @@ const initialState: State = {
showDifferences: false,
isDrawerCollapsed: false,
products: [] as ProductToCompare[],
maxNumberOfItemsToCompare: MAX_ITEMS_TO_COMPARE,
}

interface Props {
Expand All @@ -135,6 +193,31 @@ interface Props {
const ProductComparisonProvider = ({ children }: Props) => {
const [state, dispatch] = useReducer(listReducer, initialState)

const { data: appSettingsData } = useQuery(AppSettings, {
variables: {
// eslint-disable-next-line no-undef
version: process.env.VTEX_APP_VERSION,
},
ssr: false,
})

useEffect(() => {
const appSettings = JSON.parse(
pathOr(`{}`, ['appSettings', 'message'], appSettingsData)
)

dispatch({
type: 'SET_MAX_LIMIT',
args: {
maxLimit: pathOr(
MAX_ITEMS_TO_COMPARE,
['maxNumberOfItemsToCompare'],
appSettings
),
},
})
}, [appSettingsData])

useEffect(() => {
const initialProducts = localStorage.getItem('PRODUCTS_TO_COMPARE')
const productsToCompare = (initialProducts
Expand Down
1 change: 1 addition & 0 deletions react/ProductComparisonWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ToastProvider } from 'vtex.styleguide'

interface Props {
children: ReactChildren | ReactChild
maxNumberOfItemsToCompare: number | undefined
}
const ProductComparisonWrapper = ({ children }: Props) => {
const { ProductComparisonProvider } = ComparisonContext
Expand Down
2 changes: 1 addition & 1 deletion react/components/drawer/drawer.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
max-height: 500px;
bottom: 0;
left: 0;
z-index: 1;
z-index: 2;
}

.drawerContainer :global(.vtex__icon-caret-up) {
Expand Down
19 changes: 16 additions & 3 deletions react/components/productSelector/ProductSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const messages = defineMessages({
defaultMessage: '',
id: 'store/product-comparison.product-selector.compare',
},
comparisonUpperLimit: {
defaultMessage: '',
id: 'store/product-comparison.product-selector.upper-limit-exceeded',
}
})

interface Props extends InjectedIntlProps {
Expand All @@ -49,16 +53,22 @@ const ProductSelector = ({ showToast, intl }: Props) => {
const itemId = pathOr('', ['selectedItem', 'itemId'], valuesFromContext)

const isDrawerCollapsed = pathOr(false, ['isDrawerCollapsed'], comparisonData)
const productsSelected = pathOr([], ['products'], comparisonData)
const maxItemsToCompare = pathOr(
0,
['maxNumberOfItemsToCompare'],
comparisonData
)

useEffect(() => {
const selectedProducts =
productId && itemId
? find(
allPass([propEq('productId', productId), propEq('skuId', itemId)])
)(comparisonData.products)
)(productsSelected)
: []
setIsChecked(selectedProducts && !isEmpty(selectedProducts))
}, [comparisonData.products, itemId, productId])
}, [productsSelected, itemId, productId])

const showMessage = (message: string, show: boolean = true) => {
if (showToast && show) {
Expand All @@ -69,7 +79,10 @@ const ProductSelector = ({ showToast, intl }: Props) => {
}

const productSelectorChanged = (e: { target: { checked: boolean } }) => {
if (e.target.checked) {
if (e.target.checked && productsSelected.length === maxItemsToCompare) {
setIsChecked(false)
showMessage(`${intl.formatMessage(messages.comparisonUpperLimit)}`, true)
} else if (e.target.checked) {
dispatchComparison({
args: {
product: { productId: productId, skuId: itemId },
Expand Down
20 changes: 20 additions & 0 deletions react/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,23 @@
padding: unset;
border: unset;
}

@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

.productLoader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 120px;
height: 120px;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
}
6 changes: 6 additions & 0 deletions react/queries/AppSettings.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
query AppSettings($version: String) {
appSettings(app: "vtex.product-comparison", version: $version)
@context(provider: "vtex.apps-graphql") {
message
}
}
1 change: 1 addition & 0 deletions react/typings/vtex.styleguide.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ declare module 'vtex.styleguide' {
export const PageHeader: ComponentType<InputProps>
export const PageBlock: ComponentType<InputProps>
export const ToastProvider: ComponentType<InputProps>
export const Spinner: ComponentType<InputProps>
export const ToastConsumer: ComponentType<InputProps>
export const Dropdown: ComponentType<InputProps>
export const withToast
Expand Down

0 comments on commit 103ad4c

Please sign in to comment.