initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,54 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import { useContainerWidthContext } from '@woocommerce/base-context';
import { Panel } from '@woocommerce/blocks-checkout';
/**
* Internal dependencies
*/
import OrderSummaryItem from './order-summary-item.js';
import './style.scss';
const OrderSummary = ( { cartItems = [] } ) => {
const { isLarge, hasContainerWidth } = useContainerWidthContext();
if ( ! hasContainerWidth ) {
return null;
}
return (
<Panel
className="wc-block-components-order-summary"
initialOpen={ isLarge }
hasBorder={ false }
title={
<span className="wc-block-components-order-summary__button-text">
{ __( 'Order summary', 'woocommerce' ) }
</span>
}
titleTag="h2"
>
<div className="wc-block-components-order-summary__content">
{ cartItems.map( ( cartItem ) => {
return (
<OrderSummaryItem
key={ cartItem.key }
cartItem={ cartItem }
/>
);
} ) }
</div>
</Panel>
);
};
OrderSummary.propTypes = {
cartItems: PropTypes.arrayOf(
PropTypes.shape( { key: PropTypes.string.isRequired } )
),
};
export default OrderSummary;

View File

@ -0,0 +1,206 @@
/**
* External dependencies
*/
import { sprintf, _n } from '@wordpress/i18n';
import Label from '@woocommerce/base-components/label';
import ProductPrice from '@woocommerce/base-components/product-price';
import ProductName from '@woocommerce/base-components/product-name';
import {
getCurrencyFromPriceResponse,
formatPrice,
} from '@woocommerce/price-format';
import {
__experimentalApplyCheckoutFilter,
mustContain,
} from '@woocommerce/blocks-checkout';
import PropTypes from 'prop-types';
import Dinero from 'dinero.js';
import { getSetting } from '@woocommerce/settings';
import { useMemo } from '@wordpress/element';
import { useStoreCart } from '@woocommerce/base-context/hooks';
/**
* Internal dependencies
*/
import ProductBackorderBadge from '../product-backorder-badge';
import ProductImage from '../product-image';
import ProductLowStockBadge from '../product-low-stock-badge';
import ProductMetadata from '../product-metadata';
const productPriceValidation = ( value ) => mustContain( value, '<price/>' );
const OrderSummaryItem = ( { cartItem } ) => {
const {
images,
low_stock_remaining: lowStockRemaining,
show_backorder_badge: showBackorderBadge,
name: initialName,
permalink,
prices,
quantity,
short_description: shortDescription,
description: fullDescription,
item_data: itemData,
variation,
totals,
extensions,
} = cartItem;
// Prepare props to pass to the __experimentalApplyCheckoutFilter filter.
// We need to pluck out receiveCart.
// eslint-disable-next-line no-unused-vars
const { receiveCart, ...cart } = useStoreCart();
const arg = useMemo(
() => ( {
context: 'summary',
cartItem,
cart,
} ),
[ cartItem, cart ]
);
const priceCurrency = getCurrencyFromPriceResponse( prices );
const name = __experimentalApplyCheckoutFilter( {
filterName: 'itemName',
defaultValue: initialName,
extensions,
arg,
} );
const regularPriceSingle = Dinero( {
amount: parseInt( prices.raw_prices.regular_price, 10 ),
precision: parseInt( prices.raw_prices.precision, 10 ),
} )
.convertPrecision( priceCurrency.minorUnit )
.getAmount();
const priceSingle = Dinero( {
amount: parseInt( prices.raw_prices.price, 10 ),
precision: parseInt( prices.raw_prices.precision, 10 ),
} )
.convertPrecision( priceCurrency.minorUnit )
.getAmount();
const totalsCurrency = getCurrencyFromPriceResponse( totals );
let lineSubtotal = parseInt( totals.line_subtotal, 10 );
if ( getSetting( 'displayCartPricesIncludingTax', false ) ) {
lineSubtotal += parseInt( totals.line_subtotal_tax, 10 );
}
const subtotalPrice = Dinero( {
amount: lineSubtotal,
precision: totalsCurrency.minorUnit,
} ).getAmount();
const subtotalPriceFormat = __experimentalApplyCheckoutFilter( {
filterName: 'subtotalPriceFormat',
defaultValue: '<price/>',
extensions,
arg,
validation: productPriceValidation,
} );
// Allow extensions to filter how the price is displayed. Ie: prepending or appending some values.
const productPriceFormat = __experimentalApplyCheckoutFilter( {
filterName: 'cartItemPrice',
defaultValue: '<price/>',
extensions,
arg,
validation: productPriceValidation,
} );
return (
<div className="wc-block-components-order-summary-item">
<div className="wc-block-components-order-summary-item__image">
<div className="wc-block-components-order-summary-item__quantity">
<Label
label={ quantity }
screenReaderLabel={ sprintf(
/* translators: %d number of products of the same type in the cart */
_n(
'%d item',
'%d items',
quantity,
'woocommerce'
),
quantity
) }
/>
</div>
<ProductImage image={ images.length ? images[ 0 ] : {} } />
</div>
<div className="wc-block-components-order-summary-item__description">
<ProductName
disabled={ true }
name={ name }
permalink={ permalink }
/>
<ProductPrice
currency={ priceCurrency }
price={ priceSingle }
regularPrice={ regularPriceSingle }
className="wc-block-components-order-summary-item__individual-prices"
priceClassName="wc-block-components-order-summary-item__individual-price"
regularPriceClassName="wc-block-components-order-summary-item__regular-individual-price"
format={ subtotalPriceFormat }
/>
{ showBackorderBadge ? (
<ProductBackorderBadge />
) : (
!! lowStockRemaining && (
<ProductLowStockBadge
lowStockRemaining={ lowStockRemaining }
/>
)
) }
<ProductMetadata
shortDescription={ shortDescription }
fullDescription={ fullDescription }
itemData={ itemData }
variation={ variation }
/>
</div>
<span className="screen-reader-text">
{ sprintf(
/* translators: %1$d is the number of items, %2$s is the item name and %3$s is the total price including the currency symbol. */
_n(
'Total price for %1$d %2$s item: %3$s',
'Total price for %1$d %2$s items: %3$s',
quantity,
'woocommerce'
),
quantity,
name,
formatPrice( subtotalPrice, totalsCurrency )
) }
</span>
<div
className="wc-block-components-order-summary-item__total-price"
aria-hidden="true"
>
<ProductPrice
currency={ totalsCurrency }
format={ productPriceFormat }
price={ subtotalPrice }
/>
</div>
</div>
);
};
OrderSummaryItem.propTypes = {
cartItems: PropTypes.shape( {
images: PropTypes.array,
low_stock_remaining: PropTypes.number,
name: PropTypes.string.isRequired,
permalink: PropTypes.string,
prices: PropTypes.shape( {
price: PropTypes.string,
regular_price: PropTypes.string,
} ),
quantity: PropTypes.number,
summary: PropTypes.string,
variation: PropTypes.array,
} ),
};
export default OrderSummaryItem;

View File

@ -0,0 +1,104 @@
.wc-block-components-order-summary {
.wc-block-components-panel__button {
padding-top: 0;
margin-top: 0;
}
.wc-block-components-panel__content {
margin-bottom: 0;
}
}
.wc-block-components-order-summary__content {
display: table;
width: 100%;
}
.wc-block-components-order-summary-item {
@include with-translucent-border(0 0 1px);
@include font-size(small);
display: flex;
padding-bottom: 1px;
padding-top: $gap;
width: 100%;
&:first-child {
padding-top: 0;
}
&:last-child {
> div {
padding-bottom: 0;
}
&::after {
display: none;
}
}
.wc-block-components-product-metadata {
@include font-size(regular);
}
}
.wc-block-components-order-summary-item__image,
.wc-block-components-order-summary-item__description {
display: table-cell;
vertical-align: top;
}
.wc-block-components-order-summary-item__image {
width: #{$gap-large * 2};
padding-bottom: $gap;
position: relative;
> img {
width: #{$gap-large * 2};
max-width: #{$gap-large * 2};
}
}
.wc-block-components-order-summary-item__quantity {
align-items: center;
background: #fff;
border: 2px solid;
border-radius: 1em;
box-shadow: 0 0 0 2px #fff;
color: #000;
display: flex;
line-height: 1;
min-height: 20px;
padding: 0 0.4em;
position: absolute;
justify-content: center;
min-width: 20px;
right: 0;
top: 0;
transform: translate(50%, -50%);
white-space: nowrap;
z-index: 1;
}
.wc-block-components-order-summary-item__description {
padding-left: $gap-large;
padding-right: $gap-small;
padding-bottom: $gap;
p,
.wc-block-components-product-metadata {
line-height: 1.375;
margin-top: #{ ($gap-large - $gap) * 0.5 };
}
}
.wc-block-components-order-summary-item__total-price {
font-weight: bold;
margin-left: auto;
text-align: right;
}
.wc-block-components-order-summary-item__individual-prices {
display: block;
}

View File

@ -0,0 +1,43 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import { previewCart } from '@woocommerce/resource-previews';
/**
* Internal dependencies
*/
import OrderSummary from '../index';
jest.mock( '@woocommerce/base-context', () => ( {
...jest.requireActual( '@woocommerce/base-context' ),
useContainerWidthContext: () => ( {
isLarge: true,
hasContainerWidth: true,
} ),
} ) );
describe( 'Order Summary', () => {
it( 'renders correct cart line subtotal when currency has 0 decimals', async () => {
render(
<OrderSummary
cartItems={ [
{
...previewCart.items[ 0 ],
totals: {
...previewCart.items[ 0 ].totals,
// Change price format so there are no decimals.
currency_minor_unit: 0,
currency_prefix: '',
currency_suffix: '€',
line_subtotal: '16',
line_total: '18',
},
},
] }
/>
);
expect( screen.getByText( '16€' ) ).toBeTruthy();
} );
} );