Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Create Cross-Sells product list #6645

Merged
merged 26 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
aed95be
Create Cross-Sells product list
nielslange Aug 25, 2022
0539936
Show “Read more” button for out-of-stock cross-sells products
nielslange Aug 26, 2022
8085e62
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Aug 27, 2022
886d794
Update assets/js/blocks/cart/inner-blocks/cart-cross-sells-products/b…
nielslange Aug 27, 2022
370ecfb
Update assets/js/blocks/cart/cart-cross-sells-product-list/index.tsx
nielslange Aug 27, 2022
6b1ad66
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 6, 2022
51a9bac
Remove obsolete isLoading and placeholderRows
nielslange Sep 6, 2022
a1af6a1
Merge branch 'add/6558-create-cross-sells-product-list' of github.com…
nielslange Sep 6, 2022
a34b8ba
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 7, 2022
d4ae746
Fix TS errors
nielslange Sep 8, 2022
bdfb9d2
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 8, 2022
8f1c545
Rename crossSellsProduct to product
nielslange Sep 8, 2022
39733e5
Merge branch 'add/6558-create-cross-sells-product-list' of github.com…
nielslange Sep 8, 2022
eaf16c4
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 12, 2022
80ed1d8
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 12, 2022
a0afc30
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 13, 2022
9d1e746
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 14, 2022
f216982
Fix critical error
nielslange Sep 14, 2022
3634e78
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 15, 2022
fb32b5f
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 16, 2022
44efafe
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 20, 2022
c3c635f
Lock “Cart Cross-Sells products” inner block
nielslange Sep 20, 2022
8bae307
Merge branch 'add/6558-create-cross-sells-product-list' of github.com…
nielslange Sep 20, 2022
2490c9e
Merge branch 'trunk' into add/6558-create-cross-sells-product-list
nielslange Sep 20, 2022
15a3814
Update assets/js/blocks/cart/inner-blocks/cart-cross-sells-products/b…
nielslange Sep 21, 2022
a1f4d5a
Prevent moving of the Cross-Sells block
nielslange Sep 21, 2022
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 assets/js/atomic/blocks/product-elements/button/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import './style.scss';
* @param {string} [props.className] CSS Class name for the component.
* @return {*} The component.
*/
const Block = ( props ) => {
export const Block = ( props ) => {
const { className } = props;

const { parentClassName } = useInnerBlockLayoutContext();
Expand Down
14 changes: 7 additions & 7 deletions assets/js/atomic/blocks/product-elements/image/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ import './style.scss';
/**
* Product Image Block Component.
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {string} [props.imageSizing] Size of image to use.
* @param {boolean} [props.showProductLink] Whether or not to display a link to the product page.
* @param {boolean} [props.showSaleBadge] Whether or not to display the on sale badge.
* @param {string} [props.saleBadgeAlign] How should the sale badge be aligned if displayed.
* @param {boolean} [props.isDescendentOfQueryLoop] Whether or not be a children of Query Loop Block.
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {string|undefined} [props.imageSizing] Size of image to use.
* @param {boolean|undefined} [props.showProductLink] Whether or not to display a link to the product page.
* @param {boolean} [props.showSaleBadge] Whether or not to display the on sale badge.
* @param {string|undefined} [props.saleBadgeAlign] How should the sale badge be aligned if displayed.
* @param {boolean} [props.isDescendentOfQueryLoop] Whether or not be a children of Query Loop Block.
* @return {*} The component.
*/
export const Block = ( props ) => {
Expand Down
2 changes: 1 addition & 1 deletion assets/js/atomic/blocks/product-elements/price/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { withProductDataContext } from '@woocommerce/shared-hocs';
* context will be used if this is not provided.
* @return {*} The component.
*/
const Block = ( props ) => {
export const Block = ( props ) => {
const { className, textAlign } = props;
const { parentClassName } = useInnerBlockLayoutContext();
const { product } = useProductDataContext();
Expand Down
2 changes: 1 addition & 1 deletion assets/js/atomic/blocks/product-elements/rating/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import './style.scss';
* @param {string} [props.className] CSS Class name for the component.
* @return {*} The component.
*/
const Block = ( props ) => {
export const Block = ( props ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const { product } = useProductDataContext();
const rating = getAverageRating( product );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import './style.scss';
* @param {string} [props.align] Alignment of the badge.
* @return {*} The component.
*/
const Block = ( props ) => {
export const Block = ( props ) => {
const { className, align } = props;
const { parentClassName } = useInnerBlockLayoutContext();
const { product } = useProductDataContext();
Expand Down
1 change: 0 additions & 1 deletion assets/js/atomic/blocks/product-elements/title/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export interface Attributes {
headingLevel: number;
showProductLink: boolean;
linkTarget?: string;
productId: number;
align: string;
}

Expand Down
1 change: 1 addition & 0 deletions assets/js/base/context/hooks/cart/test/use-store-cart.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe( 'useStoreCart', () => {
const previewCartData = {
cartCoupons: previewCart.coupons,
cartItems: previewCart.items,
crossSellsProducts: previewCart.cross_sells,
cartFees: previewCart.fees,
cartItemsCount: previewCart.items_count,
cartItemsWeight: previewCart.items_weight,
Expand Down
4 changes: 4 additions & 0 deletions assets/js/base/context/hooks/cart/use-store-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CART_STORE_KEY as storeKey,
EMPTY_CART_COUPONS,
EMPTY_CART_ITEMS,
EMPTY_CART_CROSS_SELLS,
EMPTY_CART_FEES,
EMPTY_CART_ITEM_ERRORS,
EMPTY_CART_ERRORS,
Expand Down Expand Up @@ -99,6 +100,7 @@ export const defaultCartData: StoreCart = {
cartFees: EMPTY_CART_FEES,
cartItemsCount: 0,
cartItemsWeight: 0,
crossSellsProducts: EMPTY_CART_CROSS_SELLS,
cartNeedsPayment: true,
cartNeedsShipping: true,
cartItemErrors: EMPTY_CART_ITEM_ERRORS,
Expand Down Expand Up @@ -150,6 +152,7 @@ export const useStoreCart = (
return {
cartCoupons: previewCart.coupons,
cartItems: previewCart.items,
crossSellsProducts: previewCart.cross_sells,
cartFees: previewCart.fees,
cartItemsCount: previewCart.items_count,
cartItemsWeight: previewCart.items_weight,
Expand Down Expand Up @@ -211,6 +214,7 @@ export const useStoreCart = (
return {
cartCoupons,
cartItems: cartData.items,
crossSellsProducts: cartData.crossSells,
cartFees,
cartItemsCount: cartData.itemsCount,
cartItemsWeight: cartData.itemsWeight,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* External dependencies
*/
import {
InnerBlockLayoutContextProvider,
ProductDataContextProvider,
} from '@woocommerce/shared-context';
import { ProductResponseItem } from '@woocommerce/type-defs/product-response';

/**
* Internal dependencies
*/
import { Block as ProductImage } from '../../../atomic/blocks/product-elements/image/block';
import { Block as ProductName } from '../../../atomic/blocks/product-elements/title/block';
import { Block as ProductRating } from '../../../atomic/blocks/product-elements/rating/block';
import { Block as ProductSaleBadge } from '../../../atomic/blocks/product-elements/sale-badge/block';
import { Block as ProductPrice } from '../../../atomic/blocks/product-elements/price/block';
import { Block as ProductButton } from '../../../atomic/blocks/product-elements/button/block';
import AddToCartButton from '../../../atomic/blocks/product-elements/add-to-cart/block';

interface CrossSellsProductProps {
product: ProductResponseItem;
isLoading: boolean;
}

const CartCrossSellsProduct = ( {
product,
}: CrossSellsProductProps ): JSX.Element => {
return (
<div className="cross-sells-product">
<InnerBlockLayoutContextProvider
parentName={ 'woocommerce/cart-cross-sells-block' }
parentClassName={ 'wp-block-cart-cross-sells-product' }
>
<ProductDataContextProvider
// Setting isLoading to false, given this parameter is required.
isLoading={ false }
product={ product }
>
<div>
<ProductImage
className={ '' }
showSaleBadge={ false }
/>
<ProductName
align={ '' }
headingLevel={ 2 }
showProductLink={ true }
/>
<ProductRating />
<ProductSaleBadge />
<ProductPrice />
</div>
{ product.is_in_stock ? (
<AddToCartButton />
) : (
<ProductButton />
) }
</ProductDataContextProvider>
</InnerBlockLayoutContextProvider>
</div>
);
};

export default CartCrossSellsProduct;
37 changes: 37 additions & 0 deletions assets/js/blocks/cart/cart-cross-sells-product-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* External dependencies
*/
import { ProductResponseItem } from '@woocommerce/type-defs/product-response';

/**
* Internal dependencies
*/
import CartCrossSellsProduct from './cart-cross-sells-product';

interface CrossSellsProductListProps {
products: ProductResponseItem[];
className?: string | undefined;
columns: number;
}

const CartCrossSellsProductList = ( {
products,
columns,
}: CrossSellsProductListProps ): JSX.Element => {
const crossSellsProducts = products.map( ( product, i ) => {
if ( i >= columns ) return null;

return (
<CartCrossSellsProduct
// Setting isLoading to false, given this parameter is required.
isLoading={ false }
product={ product }
key={ product.id }
/>
);
} );

return <div>{ crossSellsProducts }</div>;
};

export default CartCrossSellsProductList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "woocommerce/cart-cross-sells-block",
"version": "1.0.0",
"title": "Cart Cross-Sells block",
"description": "Shows the Cross-Sells block.",
"category": "woocommerce",
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": true
},
"parent": [ "woocommerce/cart-items-block" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2
}
41 changes: 41 additions & 0 deletions assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* External dependencies
*/
import type { TemplateArray } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';

export const Edit = (): JSX.Element => {
const blockProps = useBlockProps( {
className: 'wc-block-cart__cross-sells',
} );
const defaultTemplate = [
[
'core/heading',
{
content: __(
'You may be interested in…',
'woo-gutenberg-products-block'
),
level: 3,
},
,
[],
],
[ 'woocommerce/cart-cross-sells-products-block', {}, [] ],
] as TemplateArray;

return (
<div { ...blockProps }>
<InnerBlocks template={ defaultTemplate } templateLock={ false } />
</div>
);
};

export const Save = (): JSX.Element => {
return (
<div { ...useBlockProps.save() }>
<InnerBlocks.Content />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { useStoreCart } from '@woocommerce/base-context/hooks';

interface Props {
children?: JSX.Element | JSX.Element[];
className?: string;
}

const FrontendBlock = ( {
children,
className = '',
}: Props ): JSX.Element | null => {
const { crossSellsProducts, cartIsLoading } = useStoreCart();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's investigate why cartIsLoading is always false for our block, it could be because it's an inner block and it's only rendered when this is finished, if so we could remove this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be great, if you could assist me troubleshooting this one.


if ( cartIsLoading || crossSellsProducts.length < 1 ) {
return null;
}

return <div className={ className }>{ children }</div>;
};

export default FrontendBlock;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { Icon, column } from '@wordpress/icons';
import { registerExperimentalBlockType } from '@woocommerce/block-settings';

/**
* Internal dependencies
*/
import { Edit, Save } from './edit';
import metadata from './block.json';

registerExperimentalBlockType( metadata, {
icon: {
src: (
<Icon
icon={ column }
className="wc-block-editor-components-block-icon"
/>
),
},
edit: Edit,
save: Save,
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "woocommerce/cart-cross-sells-products-block",
"version": "1.0.0",
"title": "Cart Cross-Sells products",
"description": "Shows the Cross-Sells products.",
"category": "woocommerce",
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false
nielslange marked this conversation as resolved.
Show resolved Hide resolved
},
"attributes": {
"columns": {
"type": "number",
"default": 3
}
},
"parent": [ "woocommerce/cart-cross-sells-block" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { useStoreCart } from '@woocommerce/base-context/hooks';

/**
* Internal dependencies
*/
import CartCrossSellsProductList from '../../cart-cross-sells-product-list';
import metadata from './block.json';

interface BlockProps {
className?: string | undefined;
columns: number;
}

const Block = ( { className, columns }: BlockProps ): JSX.Element => {
const { crossSellsProducts } = useStoreCart();

if ( typeof columns === 'undefined' ) {
columns = metadata.attributes.columns.default;
opr marked this conversation as resolved.
Show resolved Hide resolved
}

return (
<CartCrossSellsProductList
className={ className }
columns={ columns }
products={ crossSellsProducts }
/>
);
};

export default Block;
Loading