8889841cblock.json 0000644 00000001130 15052176676 0006543 0 ustar 00 {
"name": "woocommerce/checkout-shipping-method-block",
"version": "1.0.0",
"title": "Shipping Method",
"description": "Select between shipping or local pickup.",
"category": "woocommerce",
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false,
"lock": false
},
"attributes": {
"lock": {
"type": "object",
"default": {
"remove": true,
"move": true
}
}
},
"parent": [ "woocommerce/checkout-fields-block" ],
"textdomain": "woocommerce",
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2
}
edit.tsx 0000644 00000017343 15052176676 0006260 0 ustar 00 /* eslint-disable @wordpress/no-unsafe-wp-apis */
/**
* External dependencies
*/
import classnames from 'classnames';
import { __ } from '@wordpress/i18n';
import {
PanelBody,
ToggleControl,
__experimentalRadio as Radio,
__experimentalRadioGroup as RadioGroup,
} from '@wordpress/components';
import { Icon, store, shipping } from '@wordpress/icons';
import { ADMIN_URL } from '@woocommerce/settings';
import { LOCAL_PICKUP_ENABLED } from '@woocommerce/block-settings';
import {
InspectorControls,
useBlockProps,
RichText,
} from '@wordpress/block-editor';
import { useShippingData } from '@woocommerce/base-context/hooks';
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
import { useDispatch, useSelect } from '@wordpress/data';
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
/**
* Internal dependencies
*/
import {
FormStepBlock,
AdditionalFields,
AdditionalFieldsContent,
} from '../../form-step';
import { RatePrice, getLocalPickupPrices, getShippingPrices } from './shared';
import type { minMaxPrices } from './shared';
import './style.scss';
import { defaultShippingText, defaultLocalPickupText } from './constants';
const LocalPickupSelector = ( {
checked,
rate,
showPrice,
showIcon,
toggleText,
setAttributes,
}: {
checked: string;
rate: minMaxPrices;
showPrice: boolean;
showIcon: boolean;
toggleText: string;
setAttributes: ( attributes: Record< string, unknown > ) => void;
} ) => {
return (
{ showIcon === true && (
) }
setAttributes( { localPickupText: value } )
}
__unstableDisableFormats
preserveWhiteSpace
/>
{ showPrice === true && (
) }
);
};
const ShippingSelector = ( {
checked,
rate,
showPrice,
showIcon,
toggleText,
setAttributes,
}: {
checked: string;
rate: minMaxPrices;
showPrice: boolean;
showIcon: boolean;
toggleText: string;
setAttributes: ( attributes: Record< string, unknown > ) => void;
} ) => {
const Price =
rate.min === undefined ? (
{ __(
'calculated with an address',
'woo-gutenberg-products-block'
) }
) : (
);
return (
{ showIcon === true && (
) }
setAttributes( { shippingText: value } )
}
__unstableDisableFormats
preserveWhiteSpace
/>
{ showPrice === true && Price }
);
};
export const Edit = ( {
attributes,
setAttributes,
}: {
attributes: {
title: string;
description: string;
showStepNumber: boolean;
allowCreateAccount: boolean;
localPickupText: string;
shippingText: string;
showPrice: boolean;
showIcon: boolean;
className: string;
};
setAttributes: ( attributes: Record< string, unknown > ) => void;
} ): JSX.Element | null => {
const { setPrefersCollection } = useDispatch( CHECKOUT_STORE_KEY );
const { prefersCollection } = useSelect( ( select ) => {
const checkoutStore = select( CHECKOUT_STORE_KEY );
return {
prefersCollection: checkoutStore.prefersCollection(),
};
} );
const { showPrice, showIcon, className, localPickupText, shippingText } =
attributes;
const {
shippingRates,
needsShipping,
hasCalculatedShipping,
isCollectable,
} = useShippingData();
if (
! needsShipping ||
! hasCalculatedShipping ||
! shippingRates ||
! isCollectable ||
! LOCAL_PICKUP_ENABLED
) {
return null;
}
const changeView = ( method: string ) => {
if ( method === 'pickup' ) {
setPrefersCollection( true );
} else {
setPrefersCollection( false );
}
};
return (
{ __(
'Choose how this block is displayed to your customers.',
'woo-gutenberg-products-block'
) }
setAttributes( {
showIcon: ! showIcon,
} )
}
/>
setAttributes( {
showPrice: ! showPrice,
} )
}
/>
{ __(
'Methods can be made managed in your store settings.',
'woo-gutenberg-products-block'
) }
);
};
export const Save = (): JSX.Element => {
return (
);
};
shared/helpers.ts 0000644 00000005573 15052176676 0010055 0 ustar 00 /**
* External dependencies
*/
import type { CartShippingPackageShippingRate } from '@woocommerce/type-defs/cart';
import { hasCollectableRate } from '@woocommerce/base-utils';
export interface minMaxPrices {
min: CartShippingPackageShippingRate | undefined;
max: CartShippingPackageShippingRate | undefined;
}
/**
* Returns the cheapest and most expensive rate that isn't a local pickup.
*
* @param {Array|undefined} shippingRates Array of shipping Rate.
*
* @return {Object|undefined} Object with the cheapest and most expensive rates.
*/
export function getShippingPrices(
shippingRates: CartShippingPackageShippingRate[]
): minMaxPrices {
if ( shippingRates ) {
return {
min: shippingRates.reduce(
(
lowestRate: CartShippingPackageShippingRate | undefined,
currentRate: CartShippingPackageShippingRate
) => {
if ( hasCollectableRate( currentRate.method_id ) ) {
return lowestRate;
}
if (
lowestRate === undefined ||
parseInt( currentRate.price, 10 ) <
parseInt( lowestRate.price, 10 )
) {
return currentRate;
}
return lowestRate;
},
undefined
),
max: shippingRates.reduce(
(
highestRate: CartShippingPackageShippingRate | undefined,
currentRate: CartShippingPackageShippingRate
) => {
if ( hasCollectableRate( currentRate.method_id ) ) {
return highestRate;
}
if (
highestRate === undefined ||
parseInt( currentRate.price, 10 ) >
parseInt( highestRate.price, 10 )
) {
return currentRate;
}
return highestRate;
},
undefined
),
};
}
return {
min: undefined,
max: undefined,
};
}
/**
* Returns the cheapest rate that is a local pickup.
*
* @param {Array|undefined} shippingRates Array of shipping Rate.
*
* @return {Object|undefined} cheapest rate.
*/
export function getLocalPickupPrices(
shippingRates: CartShippingPackageShippingRate[]
): minMaxPrices {
if ( shippingRates ) {
return {
min: shippingRates.reduce(
(
lowestRate: CartShippingPackageShippingRate | undefined,
currentRate: CartShippingPackageShippingRate
) => {
if ( ! hasCollectableRate( currentRate.method_id ) ) {
return lowestRate;
}
if (
lowestRate === undefined ||
currentRate.price < lowestRate.price
) {
return currentRate;
}
return lowestRate;
},
undefined
),
max: shippingRates.reduce(
(
highestRate: CartShippingPackageShippingRate | undefined,
currentRate: CartShippingPackageShippingRate
) => {
if ( ! hasCollectableRate( currentRate.method_id ) ) {
return highestRate;
}
if (
highestRate === undefined ||
currentRate.price > highestRate.price
) {
return currentRate;
}
return highestRate;
},
undefined
),
};
}
return {
min: undefined,
max: undefined,
};
}
shared/index.js 0000644 00000000105 15052176676 0007472 0 ustar 00 export { RatePrice } from './rate-price';
export * from './helpers';
shared/rate-price.tsx 0000644 00000003324 15052176676 0010626 0 ustar 00 /* eslint-disable no-nested-ternary */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { getSetting } from '@woocommerce/settings';
import { createInterpolateElement } from '@wordpress/element';
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
import { FormattedMonetaryAmount } from '@woocommerce/blocks-components';
import type { CartShippingPackageShippingRate } from '@woocommerce/type-defs/cart';
export const RatePrice = ( {
minRate,
maxRate,
multiple = false,
}: {
minRate: CartShippingPackageShippingRate | undefined;
maxRate: CartShippingPackageShippingRate | undefined;
multiple?: boolean;
} ) => {
if ( minRate === undefined || maxRate === undefined ) {
return null;
}
const minRatePrice = getSetting( 'displayCartPricesIncludingTax', false )
? parseInt( minRate.price, 10 ) + parseInt( minRate.taxes, 10 )
: parseInt( minRate.price, 10 );
const maxRatePrice = getSetting( 'displayCartPricesIncludingTax', false )
? parseInt( maxRate.price, 10 ) + parseInt( maxRate.taxes, 10 )
: parseInt( maxRate.price, 10 );
const priceElement =
minRatePrice === 0 ? (
{ __( 'free', 'woo-gutenberg-products-block' ) }
) : (
);
return (
{ minRatePrice === maxRatePrice && ! multiple
? priceElement
: createInterpolateElement(
minRatePrice === 0 && maxRatePrice === 0
? ''
: __(
'from ',
'woo-gutenberg-products-block'
),
{
price: priceElement,
}
) }
);
};
attributes.tsx 0000644 00000001552 15052176676 0007514 0 ustar 00 /**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import formStepAttributes from '../../form-step/attributes';
import { defaultShippingText, defaultLocalPickupText } from './constants';
export default {
...formStepAttributes( {
defaultTitle: __( 'Shipping method', 'woo-gutenberg-products-block' ),
defaultDescription: __(
'Select how you would like to receive your order.',
'woo-gutenberg-products-block'
),
} ),
className: {
type: 'string',
default: '',
},
showIcon: {
type: 'boolean',
default: true,
},
showPrice: {
type: 'boolean',
default: true,
},
localPickupText: {
type: 'string',
default: defaultLocalPickupText,
},
shippingText: {
type: 'string',
default: defaultShippingText,
},
lock: {
type: 'object',
default: {
move: true,
remove: true,
},
},
};
frontend.tsx 0000644 00000004342 15052176676 0007145 0 ustar 00 /**
* External dependencies
*/
import classnames from 'classnames';
import { withFilteredAttributes } from '@woocommerce/shared-hocs';
import { FormStep } from '@woocommerce/blocks-components';
import { useDispatch, useSelect } from '@wordpress/data';
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
import { useShippingData } from '@woocommerce/base-context/hooks';
import { LOCAL_PICKUP_ENABLED } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import Block from './block';
import attributes from './attributes';
const FrontendBlock = ( {
title,
description,
showStepNumber,
children,
className,
showPrice,
showIcon,
shippingText,
localPickupText,
}: {
title: string;
description: string;
showStepNumber: boolean;
children: JSX.Element;
className?: string;
showPrice: boolean;
showIcon: boolean;
shippingText: string;
localPickupText: string;
} ) => {
const { checkoutIsProcessing, prefersCollection } = useSelect(
( select ) => {
const checkoutStore = select( CHECKOUT_STORE_KEY );
return {
checkoutIsProcessing: checkoutStore.isProcessing(),
prefersCollection: checkoutStore.prefersCollection(),
};
}
);
const { setPrefersCollection } = useDispatch( CHECKOUT_STORE_KEY );
const {
shippingRates,
needsShipping,
hasCalculatedShipping,
isCollectable,
} = useShippingData();
if (
! needsShipping ||
! hasCalculatedShipping ||
! shippingRates ||
! isCollectable ||
! LOCAL_PICKUP_ENABLED
) {
return null;
}
const onChange = ( method: string ) => {
if ( method === 'pickup' ) {
setPrefersCollection( true );
} else {
setPrefersCollection( false );
}
};
return (
{ children }
);
};
export default withFilteredAttributes( attributes )( FrontendBlock );
style.scss 0000644 00000002266 15052176676 0006626 0 ustar 00 .wc-block-checkout__shipping-method-container {
width: 100%;
display: flex;
gap: $gap;
justify-content: space-between;
}
.edit-post-visual-editor .wc-block-checkout__shipping-method-option,
.wc-block-checkout__shipping-method-option {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
min-height: 80px;
flex-basis: 0;
gap: 4px;
padding: 16px 12px;
color: inherit;
background-color: transparent;
border: none;
box-shadow: none !important;
outline: 1px solid currentColor;
border-radius: $universal-border-radius;
&.components-button:hover:not(:disabled),
&.components-button:focus:not(:disabled),
&:focus,
&:hover {
background-color: #d5d5d5;
border-color: #d5d5d5;
color: #333;
}
&.wc-block-checkout__shipping-method-option--selected {
outline: 3px solid currentColor;
}
}
.wc-block-checkout__shipping-method-option-icon {
fill: currentColor;
}
.wc-block-checkout__shipping-method-option-title {
@include font-size(regular, 1rem);
font-weight: bold;
}
.wc-block-checkout__shipping-method-option-price {
@include font-size(small, 1rem);
em {
text-transform: uppercase;
font-style: inherit;
}
}
index.tsx 0000644 00000000766 15052176676 0006443 0 ustar 00 /**
* External dependencies
*/
import { Icon, shipping } from '@wordpress/icons';
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { Edit, Save } from './edit';
import attributes from './attributes';
import './style.scss';
registerBlockType( 'woocommerce/checkout-shipping-method-block', {
icon: {
src: (
),
},
attributes,
edit: Edit,
save: Save,
} );
block.tsx 0000644 00000012102 15052176676 0006411 0 ustar 00 /**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useShippingData } from '@woocommerce/base-context/hooks';
import {
__experimentalRadio as Radio,
__experimentalRadioGroup as RadioGroup,
} from 'wordpress-components';
import classnames from 'classnames';
import { Icon, store, shipping } from '@wordpress/icons';
import { useEffect } from '@wordpress/element';
import { CART_STORE_KEY, VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { useDispatch, useSelect } from '@wordpress/data';
import { isPackageRateCollectable } from '@woocommerce/base-utils';
import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import { RatePrice, getLocalPickupPrices, getShippingPrices } from './shared';
import type { minMaxPrices } from './shared';
import { defaultLocalPickupText, defaultShippingText } from './constants';
import { shippingAddressHasValidationErrors } from '../../../../data/cart/utils';
const SHIPPING_RATE_ERROR = {
hidden: true,
message: __(
'Shipping options are not available',
'woo-gutenberg-products-block'
),
};
const LocalPickupSelector = ( {
checked,
rate,
showPrice,
showIcon,
toggleText,
multiple,
}: {
checked: string;
rate: minMaxPrices;
showPrice: boolean;
showIcon: boolean;
toggleText: string;
multiple: boolean;
} ) => {
return (
{ showIcon === true && (
) }
{ toggleText }
{ showPrice === true && (
) }
);
};
const ShippingSelector = ( {
checked,
rate,
showPrice,
showIcon,
toggleText,
shippingCostRequiresAddress = false,
}: {
checked: string;
rate: minMaxPrices;
showPrice: boolean;
showIcon: boolean;
shippingCostRequiresAddress: boolean;
toggleText: string;
} ) => {
const hasShippableRates = useSelect( ( select ) => {
const rates = select( CART_STORE_KEY ).getShippingRates();
return rates.some(
( { shipping_rates: shippingRate } ) =>
! shippingRate.every( isPackageRateCollectable )
);
} );
const rateShouldBeHidden =
shippingCostRequiresAddress &&
shippingAddressHasValidationErrors() &&
! hasShippableRates;
const hasShippingPrices = rate.min !== undefined && rate.max !== undefined;
const { setValidationErrors, clearValidationError } =
useDispatch( VALIDATION_STORE_KEY );
useEffect( () => {
if ( checked === 'shipping' && ! hasShippingPrices ) {
setValidationErrors( {
'shipping-rates-error': SHIPPING_RATE_ERROR,
} );
} else {
clearValidationError( 'shipping-rates-error' );
}
}, [
checked,
clearValidationError,
hasShippingPrices,
setValidationErrors,
] );
const Price =
rate.min === undefined || rateShouldBeHidden ? (
{ __(
'calculated with an address',
'woo-gutenberg-products-block'
) }
) : (
);
return (
{ showIcon === true && (
) }
{ toggleText }
{ showPrice === true && Price }
);
};
const Block = ( {
checked,
onChange,
showPrice,
showIcon,
localPickupText,
shippingText,
}: {
checked: string;
onChange: ( value: string ) => void;
showPrice: boolean;
showIcon: boolean;
localPickupText: string;
shippingText: string;
} ): JSX.Element | null => {
const { shippingRates } = useShippingData();
const shippingCostRequiresAddress = getSetting< boolean >(
'shippingCostRequiresAddress',
false
);
return (
1 }
showPrice={ showPrice }
showIcon={ showIcon }
toggleText={ localPickupText || defaultLocalPickupText }
/>
);
};
export default Block;
constants.tsx 0000644 00000000376 15052176676 0007345 0 ustar 00 /**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
export const defaultLocalPickupText = __(
'Local Pickup',
'woo-gutenberg-products-block'
);
export const defaultShippingText = __(
'Shipping',
'woo-gutenberg-products-block'
);