Skip to content

Commit

Permalink
Merge branch 'develop' into supernova/159_image_ratio
Browse files Browse the repository at this point in the history
  • Loading branch information
dpatil-magento authored Nov 15, 2019
2 parents 59e2639 + 2f9968e commit 653b2de
Show file tree
Hide file tree
Showing 16 changed files with 180 additions and 109 deletions.
20 changes: 16 additions & 4 deletions packages/peregrine/lib/talons/Breadcrumbs/useBreadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,29 @@ const getPath = (path, suffix) => {
* @param {object} props
* @param {object} props.query - the breadcrumb query
* @param {string} props.categoryId - the id of the category for which to generate breadcrumbs
* @return {{ currentCategory: string, isLoading: boolean, normalizedData: array }}
* @return {{
* currentCategory: string,
* currentCategoryPath: string,
* isLoading: boolean,
* normalizedData: array
* }}
*/
export const useBreadcrumbs = props => {
const { categoryId, query } = props;

const { data, loading } = useQuery(query, {
const { data, loading, error } = useQuery(query, {
variables: { category_id: categoryId }
});

// Default to .html for when the query has not yet returned.
const categoryUrlSuffix =
(data && data.storeConfig.category_url_suffix) || '.html';

// When we have breadcrumb data sort and normalize it for easy rendering.
const normalizedData = useMemo(() => {
if (!loading && data) {
const breadcrumbData = data.category.breadcrumbs;
const categoryUrlSuffix = data.storeConfig.category_url_suffix;

return (
breadcrumbData &&
breadcrumbData.sort(sortCrumbs).map(category => ({
Expand All @@ -42,11 +51,14 @@ export const useBreadcrumbs = props => {
}))
);
}
}, [data, loading]);
}, [categoryUrlSuffix, data, loading]);

return {
currentCategory: (data && data.category.name) || '',
currentCategoryPath:
(data && `${data.category.url_path}${categoryUrlSuffix}`) || '#',
isLoading: loading,
hasError: !!error,
normalizedData: normalizedData || []
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,41 @@ const getMediaGalleryEntries = (product, optionCodes, optionSelections) => {
return value;
};

// We only want to display breadcrumbs for one category on a PDP even if a
// product has multiple related categories. This function filters and selects
// one category id for that purpose.
const getBreadcrumbCategoryId = categories => {
const breadcrumbSet = new Set();
categories.forEach(({ breadcrumbs }) => {
// breadcrumbs can be `null`...
(breadcrumbs || []).forEach(({ category_id }) =>
breadcrumbSet.add(category_id)
);
});

// Until we can get the single canonical breadcrumb path to a product we
// will just return the first category id of the potential leaf categories.
const leafCategory = categories.find(
category => !breadcrumbSet.has(category.id)
);

// If we couldn't find a leaf category then just use the first category
// in the list for this product.
return leafCategory.id || categories[0].id;
};

export const useProductFullDetail = props => {
const { product } = props;

const [{ isAddingItem }, { addItemToCart }] = useCartContext();

const [quantity, setQuantity] = useState(INITIAL_QUANTITY);

const breadcrumbCategoryId = useMemo(
() => getBreadcrumbCategoryId(product.categories),
[product.categories]
);

const derivedOptionSelections = useMemo(
() => deriveOptionSelectionsFromProduct(product),
[product]
Expand Down Expand Up @@ -162,6 +190,7 @@ export const useProductFullDetail = props => {
};

return {
breadcrumbCategoryId,
handleAddToCart,
handleSelectionChange,
handleSetQuantity,
Expand Down
6 changes: 3 additions & 3 deletions packages/pwa-buildpack/sampleBackends.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"environments": [
{
"name": "2.3.1-venia-cloud",
"description": "Magento 2.3.1 with Venia sample data installed",
"url": "https://release-dev-231-npzdaky-zddsyhrdimyra.us-4.magentosite.cloud/"
"name": "2.3.3-venia-cloud",
"description": "Magento 2.3.3 with Venia sample data installed",
"url": "https://master-7rqtwti-mfwmkrjfqvbjk.us-4.magentosite.cloud/"
}
]
}
42 changes: 35 additions & 7 deletions packages/venia-ui/lib/components/Breadcrumbs/breadcrumbs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { Fragment, useMemo } from 'react';
import { number } from 'prop-types';
import { number, string } from 'prop-types';
import { Link, resourceUrl } from '@magento/venia-drivers';
import { mergeClasses } from '../../classify';
import defaultClasses from './breadcrumbs.css';
Expand All @@ -10,18 +10,25 @@ import GET_BREADCRUMB_DATA from '../../queries/getBreadcrumbData.graphql';
* Breadcrumbs! Generates a sorted display of category links.
*
* @param {String} props.categoryId the id of the category for which to generate breadcrumbs
* @param {String} props.currentProduct the name of the product we're currently on, if any.
*/
const Breadcrumbs = props => {
const classes = mergeClasses(defaultClasses, props.classes);

const { categoryId } = props;
const { categoryId, currentProduct } = props;

const talonProps = useBreadcrumbs({
categoryId,
query: GET_BREADCRUMB_DATA
});

const { currentCategory, isLoading, normalizedData } = talonProps;
const {
currentCategory,
currentCategoryPath,
hasError,
isLoading,
normalizedData
} = talonProps;

// For all links generate a fragment like "/ Text"
const links = useMemo(() => {
Expand All @@ -37,25 +44,46 @@ const Breadcrumbs = props => {
});
}, [classes.divider, classes.link, normalizedData]);

// Don't display anything but the empty, static height div when loading.
if (isLoading) {
// Don't display anything but the empty, static height div when loading or
// if there was an error.
if (isLoading || hasError) {
return <div className={classes.root} />;
}

// If we have a "currentProduct" it means we're on a PDP so we want the last
// category text to be a link. If we don't have a "currentProduct" we're on
// a category page so it should be regular text.
const currentCategoryLink = currentProduct ? (
<Link className={classes.link} to={resourceUrl(currentCategoryPath)}>
{currentCategory}
</Link>
) : (
<span className={classes.currentCategory}>{currentCategory}</span>
);

const currentProductNode = currentProduct ? (
<Fragment>
<span className={classes.divider}>/</span>
<span className={classes.text}>{currentProduct}</span>
</Fragment>
) : null;

return (
<div className={classes.root}>
<Link className={classes.link} to="/">
{'Home'}
</Link>
{links}
<span className={classes.divider}>/</span>
<span className={classes.currentCategory}>{currentCategory}</span>
{currentCategoryLink}
{currentProductNode}
</div>
);
};

export default Breadcrumbs;

Breadcrumbs.propTypes = {
categoryId: number.isRequired
categoryId: number.isRequired,
currentProduct: string
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
} from '@magento/peregrine';

import ProductFullDetail from '../productFullDetail';
import { Form } from 'informed';

jest.mock('../../Breadcrumbs', () => () => null);
jest.mock('../../ProductOptions', () => () => null);
jest.mock('../../../classify');
jest.mock('@magento/peregrine/lib/context/cart', () => {
Expand All @@ -28,6 +30,7 @@ const mockConfigurableProduct = {
}
}
},
categories: [{ id: 1, breadcrumbs: [{ category_id: 2 }] }],
description: 'Mock configurable product has a description!',
media_gallery_entries: [
{
Expand Down Expand Up @@ -99,9 +102,8 @@ test('Configurable Product has correct initial media gallery image count', () =>
</WindowSizeContextProvider>
);

const productFullDetailComponent = root.children[0].children[0];
const carouselComponent =
productFullDetailComponent.children[0].children[1].children[0];
const productForm = root.findByType(Form);
const carouselComponent = productForm.children[0].children[1].children[0];

expect(carouselComponent.props.images).toHaveLength(2);
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { Suspense } from 'react';
import React, { Fragment, Suspense } from 'react';
import { arrayOf, bool, number, shape, string } from 'prop-types';
import { Form } from 'informed';
import { Price } from '@magento/peregrine';
import defaultClasses from './productFullDetail.css';
import { mergeClasses } from '../../classify';

import Breadcrumbs from '../Breadcrumbs';
import Button from '../Button';
import { fullPageLoadingIndicator } from '../LoadingIndicator';
import Carousel from '../ProductImageCarousel';
Expand All @@ -24,6 +25,7 @@ const ProductFullDetail = props => {
});

const {
breadcrumbCategoryId,
handleAddToCart,
handleSelectionChange,
handleSetQuantity,
Expand All @@ -44,48 +46,60 @@ const ProductFullDetail = props => {
</Suspense>
) : null;

const breadcrumbs = breadcrumbCategoryId ? (
<Breadcrumbs
categoryId={breadcrumbCategoryId}
currentProduct={productDetails.name}
/>
) : null;

return (
<Form className={classes.root}>
<section className={classes.title}>
<h1 className={classes.productName}>{productDetails.name}</h1>
<p className={classes.productPrice}>
<Price
currencyCode={productDetails.price.currency}
value={productDetails.price.value}
<Fragment>
{breadcrumbs}
<Form className={classes.root}>
<section className={classes.title}>
<h1 className={classes.productName}>
{productDetails.name}
</h1>
<p className={classes.productPrice}>
<Price
currencyCode={productDetails.price.currency}
value={productDetails.price.value}
/>
</p>
</section>
<section className={classes.imageCarousel}>
<Carousel images={mediaGalleryEntries} />
</section>
<section className={classes.options}>{options}</section>
<section className={classes.quantity}>
<h2 className={classes.quantityTitle}>Quantity</h2>
<Quantity
initialValue={quantity}
onValueChange={handleSetQuantity}
/>
</p>
</section>
<section className={classes.imageCarousel}>
<Carousel images={mediaGalleryEntries} />
</section>
<section className={classes.options}>{options}</section>
<section className={classes.quantity}>
<h2 className={classes.quantityTitle}>Quantity</h2>
<Quantity
initialValue={quantity}
onValueChange={handleSetQuantity}
/>
</section>
<section className={classes.cartActions}>
<Button
priority="high"
onClick={handleAddToCart}
disabled={isAddToCartDisabled}
>
Add to Cart
</Button>
</section>
<section className={classes.description}>
<h2 className={classes.descriptionTitle}>
Product Description
</h2>
<RichText content={productDetails.description} />
</section>
<section className={classes.details}>
<h2 className={classes.detailsTitle}>SKU</h2>
<strong>{productDetails.sku}</strong>
</section>
</Form>
</section>
<section className={classes.cartActions}>
<Button
priority="high"
onClick={handleAddToCart}
disabled={isAddToCartDisabled}
>
Add to Cart
</Button>
</section>
<section className={classes.description}>
<h2 className={classes.descriptionTitle}>
Product Description
</h2>
<RichText content={productDetails.description} />
</section>
<section className={classes.details}>
<h2 className={classes.detailsTitle}>SKU</h2>
<strong>{productDetails.sku}</strong>
</section>
</Form>
</Fragment>
);
};

Expand Down
9 changes: 9 additions & 0 deletions packages/venia-ui/lib/fragments/productDetails.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Once graphql-ce/1027 is resolved other queries can use this fragment.
# Until then, changes to this fragment must be mirrored in
# getProductDetail.graphql.
fragment ProductDetails on ProductInterface {
__typename
categories {
id
breadcrumbs {
category_id
}
}
description {
html
}
Expand Down
1 change: 1 addition & 0 deletions packages/venia-ui/lib/queries/getBreadcrumbData.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ query getBreadcrumbData($category_id: Int!) {
}
id
name
url_path
}
}
10 changes: 9 additions & 1 deletion packages/venia-ui/lib/queries/getProductDetail.graphql
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
query productDetail($urlKey: String, $onServer: Boolean!) {
productDetail: products(filter: { url_key: { eq: $urlKey } }) {
items {
# Once graphql-ce/1027 is resolved, use a ProductDetails fragment here instead.
# Once graphql-ce/1027 is resolved, use a ProductDetails fragment
# here instead. Until then, changes to this query (within "items")
# must be mirrored in productDetails.graphql.
__typename
categories {
id
breadcrumbs {
category_id
}
}
description {
html
}
Expand Down
Loading

0 comments on commit 653b2de

Please # to comment.