8889841cblock.json000064400000001130150521766760006543 0ustar00{ "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.tsx000064400000017343150521766760006260 0ustar00/* 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.ts000064400000005573150521766760010055 0ustar00/** * 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.js000064400000000105150521766760007472 0ustar00export { RatePrice } from './rate-price'; export * from './helpers'; shared/rate-price.tsx000064400000003324150521766760010626 0ustar00/* 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.tsx000064400000001552150521766760007514 0ustar00/** * 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.tsx000064400000004342150521766760007145 0ustar00/** * 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.scss000064400000002266150521766760006626 0ustar00.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.tsx000064400000000766150521766760006443 0ustar00/** * 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.tsx000064400000012102150521766760006411 0ustar00/** * 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.tsx000064400000000376150521766760007345 0ustar00/** * External dependencies */ import { __ } from '@wordpress/i18n'; export const defaultLocalPickupText = __( 'Local Pickup', 'woo-gutenberg-products-block' ); export const defaultShippingText = __( 'Shipping', 'woo-gutenberg-products-block' );