Merge branch 'trunk' into add/orders-statuses-endpoint
This commit is contained in:
commit
f648e910db
|
@ -1,84 +0,0 @@
|
|||
name: Mirrors
|
||||
on:
|
||||
push:
|
||||
branches: ["trunk", "release/**"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'woocommerce/woocommerce'
|
||||
name: Build WooCommerce zip
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- name: Build zip
|
||||
working-directory: plugins/woocommerce
|
||||
run: bash bin/build-zip.sh
|
||||
|
||||
- name: Upload the zip file as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: woocommerce
|
||||
path: plugins/woocommerce/woocommerce.zip
|
||||
retention-days: 7
|
||||
|
||||
mirror:
|
||||
if: github.repository == 'woocommerce/woocommerce'
|
||||
name: Push to Mirror
|
||||
needs: [build]
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Create directories
|
||||
run: |
|
||||
mkdir -p monorepo
|
||||
|
||||
- name: Checkout monorepo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: monorepo
|
||||
|
||||
- name: Download WooCommerce ZIP
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: woocommerce
|
||||
path: tmp/woocommerce-build
|
||||
|
||||
- name: Extract and replace WooCommerce zip.
|
||||
working-directory: tmp/woocommerce-build
|
||||
run: |
|
||||
mkdir -p woocommerce/woocommerce-production
|
||||
unzip woocommerce.zip -d woocommerce/woocommerce-production
|
||||
mv woocommerce/woocommerce-production/woocommerce/* woocommerce/woocommerce-production
|
||||
rm -rf woocommerce/woocommerce-production/woocommerce
|
||||
|
||||
- name: Copy Composer over to production
|
||||
run: cp monorepo/plugins/woocommerce/composer.json tmp/woocommerce-build/woocommerce/woocommerce-production
|
||||
|
||||
- name: Set up mirror
|
||||
working-directory: tmp/woocommerce-build
|
||||
run: |
|
||||
touch mirrors.txt
|
||||
echo "woocommerce/woocommerce-production" >> mirrors.txt
|
||||
|
||||
- name: Push to mirror
|
||||
uses: Automattic/action-push-to-mirrors@v1
|
||||
with:
|
||||
source-directory: ${{ github.workspace }}/monorepo
|
||||
token: ${{ secrets.API_TOKEN_GITHUB }}
|
||||
username: matticbot
|
||||
working-directory: ${{ github.workspace }}/tmp/woocommerce-build
|
||||
timeout-minutes: 5 # 2021-01-18: Successful runs seem to take about half a minute.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
A fix a bug where users need to click Give feedback twice.
|
|
@ -8,6 +8,7 @@ import { createElement, useState } from '@wordpress/element';
|
|||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -38,7 +39,6 @@ import { getStoreAgeInWeeks } from '../../utils';
|
|||
* @param {boolean} props.allowTracking Whether tracking is allowed or not.
|
||||
* @param {boolean} props.resolving Are values still being resolved.
|
||||
* @param {number} props.storeAgeInWeeks The age of the store in weeks.
|
||||
* @param {Function} props.updateOptions Function to update options.
|
||||
* @param {Function} props.createNotice Function to create a snackbar.
|
||||
*/
|
||||
function _CustomerEffortScoreTracks( {
|
||||
|
@ -55,7 +55,6 @@ function _CustomerEffortScoreTracks( {
|
|||
allowTracking,
|
||||
resolving,
|
||||
storeAgeInWeeks,
|
||||
updateOptions,
|
||||
createNotice,
|
||||
} ) {
|
||||
const [ modalShown, setModalShown ] = useState( false );
|
||||
|
@ -91,12 +90,17 @@ function _CustomerEffortScoreTracks( {
|
|||
ces_location: 'inside',
|
||||
...trackProps,
|
||||
} );
|
||||
|
||||
if ( ! cesShownForActions || ! cesShownForActions.includes( action ) ) {
|
||||
updateOptions( {
|
||||
[ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [
|
||||
action,
|
||||
...( cesShownForActions || [] ),
|
||||
],
|
||||
apiFetch( {
|
||||
path: 'wc-admin/options',
|
||||
method: 'POST',
|
||||
data: {
|
||||
[ SHOWN_FOR_ACTIONS_OPTION_NAME ]: [
|
||||
action,
|
||||
...( cesShownForActions || [] ),
|
||||
],
|
||||
},
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
@ -247,11 +251,9 @@ export const CustomerEffortScoreTracks = compose(
|
|||
};
|
||||
} ),
|
||||
withDispatch( ( dispatch ) => {
|
||||
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
|
||||
const { createNotice } = dispatch( 'core/notices' );
|
||||
|
||||
return {
|
||||
updateOptions,
|
||||
createNotice,
|
||||
};
|
||||
} )
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: Adds layout to Settings, which is unreleased.
|
||||
|
|
@ -40,6 +40,10 @@ export { PostTypeContext } from './contexts/post-type-context';
|
|||
*/
|
||||
export * from './products';
|
||||
|
||||
export { default as SiteHub } from './products-app/site-hub';
|
||||
export { default as SidebarContent } from './products-app/sidebar';
|
||||
export { unlock } from './lock-unlock';
|
||||
|
||||
// Init the store
|
||||
registerProductEditorUiStore();
|
||||
|
||||
|
|
|
@ -3,8 +3,19 @@
|
|||
*/
|
||||
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
|
||||
|
||||
export const { lock, unlock } =
|
||||
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
|
||||
'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
|
||||
'@wordpress/edit-site'
|
||||
);
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getGutenbergVersion } from './utils/get-gutenberg-version';
|
||||
|
||||
const isGutenbergEnabled = getGutenbergVersion() > 0;
|
||||
const noop = () => {};
|
||||
|
||||
const { lock, unlock } = isGutenbergEnabled
|
||||
? __dangerousOptInToUnstableAPIsOnlyForCoreModules(
|
||||
'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
|
||||
'@wordpress/edit-site'
|
||||
)
|
||||
: { lock: noop, unlock: noop };
|
||||
|
||||
export { lock, unlock };
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: Adds layout to Settings, which is unreleased.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Create a warning or notice to add Gutenberg
|
|
@ -39,6 +39,7 @@
|
|||
"@types/wordpress__blocks": "11.0.7",
|
||||
"@woocommerce/settings": "^1.0.0",
|
||||
"@woocommerce/tracks": "workspace:^",
|
||||
"@woocommerce/product-editor": "workspace:^",
|
||||
"@wordpress/api-fetch": "wp-6.0",
|
||||
"@wordpress/components": "wp-6.0",
|
||||
"@wordpress/compose": "wp-6.0",
|
||||
|
@ -244,6 +245,10 @@
|
|||
"node_modules/@woocommerce/eslint-plugin/configs",
|
||||
"node_modules/@woocommerce/eslint-plugin/rules",
|
||||
"node_modules/@woocommerce/eslint-plugin/index.js",
|
||||
"node_modules/@woocommerce/product-editor/build",
|
||||
"node_modules/@woocommerce/product-editor/build-module",
|
||||
"node_modules/@woocommerce/product-editor/build-style",
|
||||
"node_modules/@woocommerce/product-editor/build-types",
|
||||
"node_modules/@woocommerce/tracks/build",
|
||||
"node_modules/@woocommerce/tracks/build-module",
|
||||
"node_modules/@woocommerce/tracks/build-types",
|
||||
|
|
|
@ -2,7 +2,38 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { isGutenbergVersionAtLeast } from './utils';
|
||||
import { Layout } from './layout';
|
||||
|
||||
const Sidebar = <div>Sidebar content goes here</div>;
|
||||
|
||||
export const SettingsEditor = () => {
|
||||
return <div style={ { padding: '20px' } }>Settings Editor</div>;
|
||||
const isRequiredGutenbergVersion = isGutenbergVersionAtLeast( 19.0 );
|
||||
|
||||
if ( ! isRequiredGutenbergVersion ) {
|
||||
return (
|
||||
// Temporary during development.
|
||||
<div style={ { margin: 'auto' } }>
|
||||
{ __(
|
||||
'Please enable Gutenberg version 19.0 or higher for this feature',
|
||||
'woocommerce'
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout
|
||||
route={ {
|
||||
key: 'settings',
|
||||
areas: { sidebar: Sidebar },
|
||||
widths: {},
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, Fragment, useRef } from '@wordpress/element';
|
||||
import { unlock, SiteHub, SidebarContent } from '@woocommerce/product-editor';
|
||||
import {
|
||||
useViewportMatch,
|
||||
useResizeObserver,
|
||||
useReducedMotion,
|
||||
} from '@wordpress/compose';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
// @ts-expect-error missing type.
|
||||
EditorSnackbars,
|
||||
// @ts-expect-error missing type.
|
||||
privateApis as editorPrivateApis,
|
||||
} from '@wordpress/editor';
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import {
|
||||
// @ts-expect-error missing type.
|
||||
__unstableMotion as motion,
|
||||
// @ts-expect-error missing type.
|
||||
__unstableAnimatePresence as AnimatePresence,
|
||||
} from '@wordpress/components';
|
||||
|
||||
type Route = {
|
||||
key: string;
|
||||
areas: {
|
||||
sidebar: React.JSX.Element | React.FunctionComponent;
|
||||
content?: React.JSX.Element | React.FunctionComponent;
|
||||
edit?: React.JSX.Element | React.FunctionComponent;
|
||||
mobile?: React.JSX.Element | React.FunctionComponent | boolean;
|
||||
preview?: boolean;
|
||||
};
|
||||
widths?: {
|
||||
content?: number;
|
||||
edit?: number;
|
||||
sidebar?: number;
|
||||
};
|
||||
};
|
||||
|
||||
const { NavigableRegion } = unlock( editorPrivateApis );
|
||||
|
||||
const ANIMATION_DURATION = 0.3;
|
||||
|
||||
type LayoutProps = {
|
||||
route: Route;
|
||||
};
|
||||
|
||||
export function Layout( { route }: LayoutProps ) {
|
||||
const [ fullResizer ] = useResizeObserver();
|
||||
const toggleRef = useRef< HTMLAnchorElement >( null );
|
||||
const isMobileViewport = useViewportMatch( 'medium', '<' );
|
||||
const disableMotion = useReducedMotion();
|
||||
|
||||
const { key: routeKey, areas, widths } = route;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ fullResizer }
|
||||
<div className="edit-site-layout">
|
||||
<div className="edit-site-layout__content">
|
||||
{ /*
|
||||
The NavigableRegion must always be rendered and not use
|
||||
`inert` otherwise `useNavigateRegions` will fail.
|
||||
*/ }
|
||||
{ ( ! isMobileViewport || ! areas.mobile ) && (
|
||||
<NavigableRegion
|
||||
ariaLabel={ __( 'Navigation', 'woocommerce' ) }
|
||||
className="edit-site-layout__sidebar-region"
|
||||
>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={ { opacity: 0 } }
|
||||
animate={ { opacity: 1 } }
|
||||
exit={ { opacity: 0 } }
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration:
|
||||
// Disable transition in mobile to emulate a full page transition.
|
||||
disableMotion || isMobileViewport
|
||||
? 0
|
||||
: ANIMATION_DURATION,
|
||||
ease: 'easeOut',
|
||||
} }
|
||||
className="edit-site-layout__sidebar"
|
||||
>
|
||||
<SiteHub
|
||||
ref={ toggleRef }
|
||||
isTransparent={ false }
|
||||
/>
|
||||
<SidebarContent routeKey={ routeKey }>
|
||||
{ areas.sidebar }
|
||||
</SidebarContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</NavigableRegion>
|
||||
) }
|
||||
|
||||
<EditorSnackbars />
|
||||
|
||||
{ ! isMobileViewport && areas.content && (
|
||||
<div
|
||||
className="edit-site-layout__area"
|
||||
style={ {
|
||||
maxWidth: widths?.content,
|
||||
} }
|
||||
>
|
||||
{ areas.content }
|
||||
</div>
|
||||
) }
|
||||
|
||||
{ ! isMobileViewport && areas.edit && (
|
||||
<div
|
||||
className="edit-site-layout__area"
|
||||
style={ {
|
||||
maxWidth: widths?.edit,
|
||||
} }
|
||||
>
|
||||
{ areas.edit }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
|
||||
export function isGutenbergVersionAtLeast( version: number ) {
|
||||
const adminSettings: { gutenberg_version?: string } = getSetting( 'admin' );
|
||||
if ( adminSettings.gutenberg_version ) {
|
||||
return parseFloat( adminSettings?.gutenberg_version ) >= version;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
declare module '@woocommerce/settings' {
|
||||
export declare function getAdminLink( path: string ): string;
|
||||
export declare function getSetting< T >(
|
||||
name: string,
|
||||
fallback?: unknown,
|
||||
filter = ( val: unknown, fb: unknown ) =>
|
||||
typeof val !== 'undefined' ? val : fb
|
||||
): T;
|
||||
export declare function isWpVersion(
|
||||
version: string,
|
||||
operator: '>' | '>=' | '=' | '<' | '<='
|
||||
): boolean;
|
||||
}
|
|
@ -44,6 +44,37 @@ register_woocommerce_admin_test_helper_rest_route(
|
|||
)
|
||||
);
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/options',
|
||||
'wca_test_helper_update_option',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'args' => array(
|
||||
'options' => array(
|
||||
'description' => 'Array of options to update.',
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'option_name' => array(
|
||||
'description' => 'The name of the option to update.',
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
'option_value' => array(
|
||||
'description' => 'The new value for the option.',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* A helper to delete options.
|
||||
*
|
||||
|
@ -99,3 +130,26 @@ function wca_test_helper_get_options( $request ) {
|
|||
return new WP_REST_Response( $options, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update WordPress options. Supports single or batch updates.
|
||||
*
|
||||
* @param WP_REST_Request $request The full request data.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
function wca_test_helper_update_option( $request ) {
|
||||
$data = $request->get_json_params();
|
||||
$response = array();
|
||||
|
||||
foreach ( $data['options'] as $option ) {
|
||||
if ( ! isset( $option['option_name'] ) || ! isset( $option['option_value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
update_option( $option['option_name'], $option['option_value'] );
|
||||
$response[] = array(
|
||||
'option_name' => $option['option_name'],
|
||||
'option_value' => $option['option_value'],
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response, 200 );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
Update WC Admin Test Helper's Reset Onboarding Wizard tool
|
|
@ -101,14 +101,43 @@ export function* triggerWcaInstall() {
|
|||
export function* resetOnboardingWizard() {
|
||||
yield runCommand( 'Reset Onboarding Wizard', function* () {
|
||||
const optionsToDelete = [
|
||||
'woocommerce_task_list_tracked_completed_tasks',
|
||||
'woocommerce_onboarding_profile',
|
||||
'_transient_wc_onboarding_themes',
|
||||
'woocommerce_task_list_tracked_completed_tasks',
|
||||
'woocommerce_private_link',
|
||||
'woocommerce_share_key',
|
||||
'woocommerce_store_pages_only',
|
||||
];
|
||||
|
||||
const defaultOptions = {
|
||||
woocommerce_allow_tracking: 'no',
|
||||
woocommerce_default_country: 'US:CA',
|
||||
woocommerce_currency: 'USD',
|
||||
woocommerce_currency_pos: 'left',
|
||||
woocommerce_price_thousand_sep: ',',
|
||||
woocommerce_price_decimal_sep: '.',
|
||||
woocommerce_price_num_decimals: '2',
|
||||
woocommerce_coming_soon: 'no',
|
||||
};
|
||||
|
||||
// Delete existing options
|
||||
yield apiFetch( {
|
||||
method: 'DELETE',
|
||||
path: `${ API_NAMESPACE }/options/${ optionsToDelete.join( ',' ) }`,
|
||||
} );
|
||||
|
||||
// Execute batch update of options
|
||||
yield apiFetch( {
|
||||
method: 'POST',
|
||||
path: `${ API_NAMESPACE }/options`,
|
||||
data: {
|
||||
options: Object.entries( defaultOptions ).map(
|
||||
( [ option_name, option_value ] ) => ( {
|
||||
option_name,
|
||||
option_value,
|
||||
} )
|
||||
),
|
||||
},
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
@ -121,6 +121,40 @@ const restrictedImports = [
|
|||
},
|
||||
];
|
||||
|
||||
const coreModules = [
|
||||
'@woocommerce/block-data',
|
||||
'@woocommerce/blocks-checkout',
|
||||
'@woocommerce/blocks-components',
|
||||
'@woocommerce/price-format',
|
||||
'@woocommerce/settings',
|
||||
'@woocommerce/shared-context',
|
||||
'@woocommerce/shared-hocs',
|
||||
'@woocommerce/tracks',
|
||||
'@woocommerce/data',
|
||||
'@wordpress/a11y',
|
||||
'@wordpress/api-fetch',
|
||||
'@wordpress/block-editor',
|
||||
'@wordpress/compose',
|
||||
'@wordpress/data',
|
||||
'@wordpress/core-data',
|
||||
'@wordpress/editor',
|
||||
'@wordpress/escape-html',
|
||||
'@wordpress/hooks',
|
||||
'@wordpress/keycodes',
|
||||
'@wordpress/url',
|
||||
'@woocommerce/blocks-test-utils',
|
||||
'@woocommerce/e2e-utils',
|
||||
'babel-jest',
|
||||
'dotenv',
|
||||
'jest-environment-puppeteer',
|
||||
'lodash/kebabCase',
|
||||
'lodash',
|
||||
'prop-types',
|
||||
'react',
|
||||
'requireindex',
|
||||
'react-transition-group',
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
|
@ -151,39 +185,7 @@ module.exports = {
|
|||
// List of modules that are externals in our webpack config.
|
||||
// This helps the `import/no-extraneous-dependencies` and
|
||||
//`import/no-unresolved` rules account for them.
|
||||
'import/core-modules': [
|
||||
'@woocommerce/block-data',
|
||||
'@woocommerce/blocks-checkout',
|
||||
'@woocommerce/blocks-components',
|
||||
'@woocommerce/price-format',
|
||||
'@woocommerce/settings',
|
||||
'@woocommerce/shared-context',
|
||||
'@woocommerce/shared-hocs',
|
||||
'@woocommerce/tracks',
|
||||
'@woocommerce/data',
|
||||
'@wordpress/a11y',
|
||||
'@wordpress/api-fetch',
|
||||
'@wordpress/block-editor',
|
||||
'@wordpress/compose',
|
||||
'@wordpress/data',
|
||||
'@wordpress/core-data',
|
||||
'@wordpress/editor',
|
||||
'@wordpress/escape-html',
|
||||
'@wordpress/hooks',
|
||||
'@wordpress/keycodes',
|
||||
'@wordpress/url',
|
||||
'@woocommerce/blocks-test-utils',
|
||||
'@woocommerce/e2e-utils',
|
||||
'babel-jest',
|
||||
'dotenv',
|
||||
'jest-environment-puppeteer',
|
||||
'lodash/kebabCase',
|
||||
'lodash',
|
||||
'prop-types',
|
||||
'react',
|
||||
'requireindex',
|
||||
'react-transition-group',
|
||||
],
|
||||
'import/core-modules': coreModules,
|
||||
'import/resolver': {
|
||||
node: {},
|
||||
webpack: {},
|
||||
|
@ -301,6 +303,7 @@ module.exports = {
|
|||
typescript: {}, // this loads <rootdir>/tsconfig.json to eslint
|
||||
},
|
||||
'import/core-modules': [
|
||||
...coreModules,
|
||||
// We should lint these modules imports, but the types are way out of date.
|
||||
// To support us not inadvertently introducing new import errors this lint exists, but to avoid
|
||||
// having to fix hundreds of import errors for @wordpress packages we ignore them.
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"https://github.com/WP-API/Basic-Auth/archive/master.zip",
|
||||
"https://downloads.wordpress.org/plugin/wordpress-importer.0.8.zip",
|
||||
"./tests/mocks/woo-test-helper",
|
||||
"../woocommerce/tests/e2e-pw/bin/test-helper-apis.php",
|
||||
"../woocommerce"
|
||||
],
|
||||
"env": {
|
||||
|
|
|
@ -8,11 +8,9 @@ export { default as ProductDetails } from './product-details';
|
|||
export { default as ProductImage } from './product-image';
|
||||
export { default as ProductLowStockBadge } from './product-low-stock-badge';
|
||||
export { default as ProductSummary } from './product-summary';
|
||||
export { default as PickupLocation } from './pickup-location';
|
||||
export { default as ProductMetadata } from './product-metadata';
|
||||
export { default as ProductSaleBadge } from './product-sale-badge';
|
||||
export { default as ReturnToCartButton } from './return-to-cart-button';
|
||||
export { default as ShippingLocation } from './shipping-location';
|
||||
export { default as ShippingRatesControl } from './shipping-rates-control';
|
||||
export { default as ShippingRatesControlPackage } from './shipping-rates-control-package';
|
||||
export { default as PaymentMethodIcons } from './payment-method-icons';
|
||||
|
|
|
@ -13,10 +13,12 @@ import './style.scss';
|
|||
|
||||
interface OrderSummaryProps {
|
||||
cartItems: CartItem[];
|
||||
disableProductDescriptions: boolean;
|
||||
}
|
||||
|
||||
const OrderSummary = ( {
|
||||
cartItems = [],
|
||||
disableProductDescriptions = false,
|
||||
}: OrderSummaryProps ): null | JSX.Element => {
|
||||
const { isLarge, hasContainerWidth } = useContainerWidthContext();
|
||||
|
||||
|
@ -34,6 +36,9 @@ const OrderSummary = ( {
|
|||
{ cartItems.map( ( cartItem ) => {
|
||||
return (
|
||||
<OrderSummaryItem
|
||||
disableProductDescriptions={
|
||||
disableProductDescriptions
|
||||
}
|
||||
key={ cartItem.key }
|
||||
cartItem={ cartItem }
|
||||
/>
|
||||
|
|
|
@ -30,9 +30,13 @@ import ProductMetadata from '../product-metadata';
|
|||
|
||||
interface OrderSummaryProps {
|
||||
cartItem: CartItem;
|
||||
disableProductDescriptions: boolean;
|
||||
}
|
||||
|
||||
const OrderSummaryItem = ( { cartItem }: OrderSummaryProps ): JSX.Element => {
|
||||
const OrderSummaryItem = ( {
|
||||
cartItem,
|
||||
disableProductDescriptions,
|
||||
}: OrderSummaryProps ): JSX.Element => {
|
||||
const {
|
||||
images,
|
||||
low_stock_remaining: lowStockRemaining,
|
||||
|
@ -122,6 +126,18 @@ const OrderSummaryItem = ( { cartItem }: OrderSummaryProps ): JSX.Element => {
|
|||
arg,
|
||||
} );
|
||||
|
||||
const productMetaProps = disableProductDescriptions
|
||||
? {
|
||||
itemData,
|
||||
variation,
|
||||
}
|
||||
: {
|
||||
itemData,
|
||||
variation,
|
||||
shortDescription,
|
||||
fullDescription,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ clsx(
|
||||
|
@ -174,12 +190,7 @@ const OrderSummaryItem = ( { cartItem }: OrderSummaryProps ): JSX.Element => {
|
|||
/>
|
||||
)
|
||||
) }
|
||||
<ProductMetadata
|
||||
shortDescription={ shortDescription }
|
||||
fullDescription={ fullDescription }
|
||||
itemData={ itemData }
|
||||
variation={ variation }
|
||||
/>
|
||||
<ProductMetadata { ...productMetaProps } />
|
||||
</div>
|
||||
<span className="screen-reader-text">
|
||||
{ sprintf(
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { isObject, objectHasProp } from '@woocommerce/types';
|
||||
import { isPackageRateCollectable } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
* Shows a formatted pickup location.
|
||||
*/
|
||||
const PickupLocation = (): JSX.Element | null => {
|
||||
const { pickupAddress } = useSelect( ( select ) => {
|
||||
const cartShippingRates = select( 'wc/store/cart' ).getShippingRates();
|
||||
|
||||
const flattenedRates = cartShippingRates.flatMap(
|
||||
( cartShippingRate ) => cartShippingRate.shipping_rates
|
||||
);
|
||||
const selectedCollectableRate = flattenedRates.find(
|
||||
( rate ) => rate.selected && isPackageRateCollectable( rate )
|
||||
);
|
||||
|
||||
// If the rate has an address specified in its metadata.
|
||||
if (
|
||||
isObject( selectedCollectableRate ) &&
|
||||
objectHasProp( selectedCollectableRate, 'meta_data' )
|
||||
) {
|
||||
const selectedRateMetaData = selectedCollectableRate.meta_data.find(
|
||||
( meta ) => meta.key === 'pickup_address'
|
||||
);
|
||||
if (
|
||||
isObject( selectedRateMetaData ) &&
|
||||
objectHasProp( selectedRateMetaData, 'value' ) &&
|
||||
selectedRateMetaData.value
|
||||
) {
|
||||
const selectedRatePickupAddress = selectedRateMetaData.value;
|
||||
return {
|
||||
pickupAddress: selectedRatePickupAddress,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if ( isObject( selectedCollectableRate ) ) {
|
||||
return {
|
||||
pickupAddress: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
pickupAddress: undefined,
|
||||
};
|
||||
} );
|
||||
|
||||
// If the method does not contain an address, or the method supporting collection was not found, return early.
|
||||
if ( typeof pickupAddress === 'undefined' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Show the pickup method's name if we don't have an address to show.
|
||||
return (
|
||||
<span className="wc-block-components-shipping-address">
|
||||
{ sprintf(
|
||||
/* translators: %s: shipping method name, e.g. "Amazon Locker" */
|
||||
__( 'Collection from %s', 'woocommerce' ),
|
||||
pickupAddress
|
||||
) + ' ' }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickupLocation;
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { CART_STORE_KEY, CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import PickupLocation from '@woocommerce/base-components/cart-checkout/pickup-location';
|
||||
|
||||
jest.mock( '@woocommerce/settings', () => {
|
||||
const originalModule = jest.requireActual( '@woocommerce/settings' );
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore We know @woocommerce/settings is an object.
|
||||
...originalModule,
|
||||
getSetting: ( setting: string, ...rest: unknown[] ) => {
|
||||
if ( setting === 'localPickupEnabled' ) {
|
||||
return true;
|
||||
}
|
||||
if ( setting === 'collectableMethodIds' ) {
|
||||
return [ 'pickup_location' ];
|
||||
}
|
||||
return originalModule.getSetting( setting, ...rest );
|
||||
},
|
||||
};
|
||||
} );
|
||||
describe( 'PickupLocation', () => {
|
||||
it( `renders an address if one is set in the methods metadata`, async () => {
|
||||
dispatch( CHECKOUT_STORE_KEY ).setPrefersCollection( true );
|
||||
|
||||
// Deselect the default selected rate and select pickup_location:1 rate.
|
||||
const currentlySelectedIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.selected
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
currentlySelectedIndex
|
||||
].selected = false;
|
||||
const pickupRateIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.method_id === 'pickup_location'
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].selected = true;
|
||||
|
||||
dispatch( CART_STORE_KEY ).receiveCart( previewCart );
|
||||
|
||||
render( <PickupLocation /> );
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Collection from 123 Easy Street, New York, 12345/
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
it( 'renders no address if one is not set in the methods metadata', async () => {
|
||||
dispatch( CHECKOUT_STORE_KEY ).setPrefersCollection( true );
|
||||
|
||||
// Deselect the default selected rate and select pickup_location:1 rate.
|
||||
const currentlySelectedIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.selected
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
currentlySelectedIndex
|
||||
].selected = false;
|
||||
const pickupRateIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.rate_id === 'pickup_location:2'
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].selected = true;
|
||||
|
||||
// Set the pickup_location metadata value to an empty string in the selected pickup rate.
|
||||
const addressKeyIndex = previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].meta_data.findIndex(
|
||||
( metaData ) => metaData.key === 'pickup_address'
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].meta_data[ addressKeyIndex ].value = '';
|
||||
|
||||
dispatch( CART_STORE_KEY ).receiveCart( previewCart );
|
||||
|
||||
render( <PickupLocation /> );
|
||||
expect(
|
||||
screen.queryByText( /Collection from / )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
} );
|
|
@ -31,7 +31,6 @@ export const ShippingCalculatorButton = ( {
|
|||
<Button
|
||||
render={ <span /> }
|
||||
className="wc-block-components-totals-shipping__change-address__link"
|
||||
id="wc-block-components-totals-shipping__change-address__link"
|
||||
onClick={ ( e ) => {
|
||||
e.preventDefault();
|
||||
setIsShippingCalculatorOpen( ! isShippingCalculatorOpen );
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wc-block-components-totals-shipping__change-address__link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wc-block-components-shipping-calculator-address__button {
|
||||
width: 100%;
|
||||
margin-top: em($gap-large);
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
|
||||
interface ShippingLocationProps {
|
||||
formattedLocation: string | null;
|
||||
}
|
||||
|
||||
// Shows a formatted shipping location.
|
||||
const ShippingLocation = ( {
|
||||
formattedLocation,
|
||||
}: ShippingLocationProps ): JSX.Element | null => {
|
||||
if ( ! formattedLocation ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="wc-block-components-shipping-address">
|
||||
{ sprintf(
|
||||
/* translators: %s location. */
|
||||
__( 'Delivers to %s', 'woocommerce' ),
|
||||
formattedLocation
|
||||
) + ' ' }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingLocation;
|
|
@ -1,165 +1,55 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import clsx from 'clsx';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { TotalsItem } from '@woocommerce/blocks-components';
|
||||
import type { Currency } from '@woocommerce/types';
|
||||
import { ShippingVia } from '@woocommerce/base-components/cart-checkout/totals/shipping/shipping-via';
|
||||
import {
|
||||
isAddressComplete,
|
||||
isPackageRateCollectable,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { ShippingCalculatorContext } from '@woocommerce/base-components/cart-checkout/shipping-calculator/context';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import { hasShippingRate } from '@woocommerce/base-utils';
|
||||
import { useStoreCart } from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ShippingCalculator } from '../../shipping-calculator';
|
||||
import {
|
||||
hasShippingRate,
|
||||
getTotalShippingValue,
|
||||
areShippingMethodsMissing,
|
||||
} from './utils';
|
||||
import ShippingPlaceholder from './shipping-placeholder';
|
||||
import ShippingAddress from './shipping-address';
|
||||
import ShippingRateSelector from './shipping-rate-selector';
|
||||
import { ShippingVia } from './shipping-via';
|
||||
import { ShippingAddress } from './shipping-address';
|
||||
import { renderShippingTotalValue } from './utils';
|
||||
import './style.scss';
|
||||
|
||||
export interface TotalShippingProps {
|
||||
currency: Currency;
|
||||
values: {
|
||||
total_shipping: string;
|
||||
total_shipping_tax: string;
|
||||
}; // Values in use
|
||||
showCalculator?: boolean; //Whether to display the rate selector below the shipping total.
|
||||
showRateSelector?: boolean; // Whether to show shipping calculator or not.
|
||||
className?: string;
|
||||
isCheckout?: boolean;
|
||||
label?: string;
|
||||
placeholder?: React.ReactNode;
|
||||
collaterals?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const TotalsShipping = ( {
|
||||
currency,
|
||||
values,
|
||||
showCalculator = true,
|
||||
showRateSelector = true,
|
||||
isCheckout = false,
|
||||
className,
|
||||
}: TotalShippingProps ): JSX.Element => {
|
||||
const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] =
|
||||
useState( false );
|
||||
const {
|
||||
shippingAddress,
|
||||
cartHasCalculatedShipping,
|
||||
shippingRates,
|
||||
isLoadingRates,
|
||||
} = useStoreCart();
|
||||
const totalShippingValue = getTotalShippingValue( values );
|
||||
const hasRates = hasShippingRate( shippingRates ) || totalShippingValue > 0;
|
||||
const showShippingCalculatorForm =
|
||||
showCalculator && isShippingCalculatorOpen;
|
||||
|
||||
const prefersCollection = useSelect( ( select ) => {
|
||||
return select( CHECKOUT_STORE_KEY ).prefersCollection();
|
||||
} );
|
||||
const selectedShippingRates = shippingRates.flatMap(
|
||||
( shippingPackage ) => {
|
||||
return shippingPackage.shipping_rates
|
||||
.filter(
|
||||
( rate ) =>
|
||||
// If the shopper prefers collection, the rate is collectable AND selected.
|
||||
( prefersCollection &&
|
||||
isPackageRateCollectable( rate ) &&
|
||||
rate.selected ) ||
|
||||
// Or the shopper does not prefer collection and the rate is selected
|
||||
( ! prefersCollection && rate.selected )
|
||||
)
|
||||
.flatMap( ( rate ) => rate.name );
|
||||
}
|
||||
);
|
||||
const addressComplete = isAddressComplete( shippingAddress, [
|
||||
'state',
|
||||
'country',
|
||||
'postcode',
|
||||
'city',
|
||||
] );
|
||||
const shippingMethodsMissing = areShippingMethodsMissing(
|
||||
hasRates,
|
||||
prefersCollection,
|
||||
shippingRates
|
||||
);
|
||||
|
||||
const valueToDisplay =
|
||||
totalShippingValue === 0 ? (
|
||||
<strong>{ __( 'Free', 'woocommerce' ) }</strong>
|
||||
) : (
|
||||
totalShippingValue
|
||||
);
|
||||
|
||||
label = __( 'Shipping', 'woocommerce' ),
|
||||
placeholder = null,
|
||||
collaterals = null,
|
||||
}: TotalShippingProps ): JSX.Element | null => {
|
||||
const { cartTotals, shippingRates } = useStoreCart();
|
||||
const hasRates = hasShippingRate( shippingRates );
|
||||
return (
|
||||
<div
|
||||
className={ clsx(
|
||||
'wc-block-components-totals-shipping',
|
||||
className
|
||||
) }
|
||||
>
|
||||
<ShippingCalculatorContext.Provider
|
||||
value={ {
|
||||
showCalculator,
|
||||
shippingCalculatorID: 'shipping-calculator-form-wrapper',
|
||||
isShippingCalculatorOpen,
|
||||
setIsShippingCalculatorOpen,
|
||||
} }
|
||||
>
|
||||
<TotalsItem
|
||||
label={ __( 'Delivery', 'woocommerce' ) }
|
||||
value={
|
||||
! shippingMethodsMissing && cartHasCalculatedShipping
|
||||
? // if address is not complete, display the link to add an address.
|
||||
valueToDisplay
|
||||
: ( ! addressComplete || isCheckout ) && (
|
||||
<ShippingPlaceholder
|
||||
showCalculator={ showCalculator }
|
||||
isCheckout={ isCheckout }
|
||||
addressProvided={ addressComplete }
|
||||
/>
|
||||
)
|
||||
}
|
||||
description={
|
||||
( ! shippingMethodsMissing &&
|
||||
cartHasCalculatedShipping ) ||
|
||||
// If address is complete, display the shipping address.
|
||||
( addressComplete && ! isCheckout ) ? (
|
||||
<>
|
||||
<ShippingVia
|
||||
selectedShippingRates={
|
||||
selectedShippingRates
|
||||
}
|
||||
/>
|
||||
<ShippingAddress
|
||||
shippingAddress={ shippingAddress }
|
||||
/>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
currency={ currency }
|
||||
/>
|
||||
{ showShippingCalculatorForm && <ShippingCalculator /> }
|
||||
{ showRateSelector &&
|
||||
cartHasCalculatedShipping &&
|
||||
! showShippingCalculatorForm && (
|
||||
<ShippingRateSelector
|
||||
hasRates={ hasRates }
|
||||
shippingRates={ shippingRates }
|
||||
isLoadingRates={ isLoadingRates }
|
||||
isAddressComplete={ addressComplete }
|
||||
shippingAddress={ shippingAddress }
|
||||
/>
|
||||
) }
|
||||
</ShippingCalculatorContext.Provider>
|
||||
<div className="wc-block-components-totals-shipping">
|
||||
<TotalsItem
|
||||
label={ label }
|
||||
value={
|
||||
hasRates
|
||||
? renderShippingTotalValue( cartTotals )
|
||||
: placeholder
|
||||
}
|
||||
description={
|
||||
<>
|
||||
<ShippingVia />
|
||||
<ShippingAddress />
|
||||
{ collaterals && (
|
||||
<div className="wc-block-components-totals-shipping__collaterals">
|
||||
{ collaterals }
|
||||
</div>
|
||||
) }
|
||||
</>
|
||||
}
|
||||
currency={ getCurrencyFromPriceResponse( cartTotals ) }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,43 +1,46 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { formatShippingAddress } from '@woocommerce/base-utils';
|
||||
import { ShippingAddress as ShippingAddressType } from '@woocommerce/settings';
|
||||
import {
|
||||
ShippingLocation,
|
||||
PickupLocation,
|
||||
ShippingCalculatorButton,
|
||||
} from '@woocommerce/base-components/cart-checkout';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { useStoreCart } from '@woocommerce/base-context';
|
||||
import { ShippingCalculatorButton } from '@woocommerce/base-components/cart-checkout';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
export interface ShippingAddressProps {
|
||||
shippingAddress: ShippingAddressType;
|
||||
}
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getPickupLocation } from './utils';
|
||||
|
||||
export const ShippingAddress = ( {
|
||||
shippingAddress,
|
||||
}: ShippingAddressProps ): JSX.Element | null => {
|
||||
export const ShippingAddress = (): JSX.Element => {
|
||||
const { shippingRates, shippingAddress } = useStoreCart();
|
||||
const prefersCollection = useSelect( ( select ) =>
|
||||
select( CHECKOUT_STORE_KEY ).prefersCollection()
|
||||
);
|
||||
|
||||
const hasFormattedAddress = !! formatShippingAddress( shippingAddress );
|
||||
const formattedAddress = prefersCollection
|
||||
? getPickupLocation( shippingRates )
|
||||
: formatShippingAddress( shippingAddress );
|
||||
|
||||
const addressLabel = prefersCollection
|
||||
? /* translators: %s location. */
|
||||
__( 'Collection from %s', 'woocommerce' )
|
||||
: /* translators: %s location. */
|
||||
__( 'Delivers to %s', 'woocommerce' );
|
||||
|
||||
const calculatorLabel =
|
||||
! formattedAddress || prefersCollection
|
||||
? __( 'Enter address to check delivery options', 'woocommerce' )
|
||||
: __( 'Change address', 'woocommerce' );
|
||||
|
||||
const label = hasFormattedAddress
|
||||
? __( 'Change address', 'woocommerce' )
|
||||
: __( 'Enter address to check delivery options', 'woocommerce' );
|
||||
const formattedLocation = formatShippingAddress( shippingAddress );
|
||||
return (
|
||||
<>
|
||||
{ prefersCollection ? (
|
||||
<PickupLocation />
|
||||
) : (
|
||||
<ShippingLocation formattedLocation={ formattedLocation } />
|
||||
) }
|
||||
<ShippingCalculatorButton label={ label } />
|
||||
</>
|
||||
<div className="wc-block-components-shipping-address">
|
||||
{ formattedAddress
|
||||
? sprintf( addressLabel, formattedAddress ) + ' '
|
||||
: null }
|
||||
<ShippingCalculatorButton label={ calculatorLabel } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { ShippingCalculatorButton } from '@woocommerce/base-components/cart-checkout';
|
||||
|
||||
export interface ShippingPlaceholderProps {
|
||||
showCalculator: boolean;
|
||||
isCheckout?: boolean;
|
||||
addressProvided: boolean;
|
||||
}
|
||||
|
||||
export const ShippingPlaceholder = ( {
|
||||
showCalculator,
|
||||
addressProvided,
|
||||
isCheckout = false,
|
||||
}: ShippingPlaceholderProps ): JSX.Element => {
|
||||
if ( ! showCalculator ) {
|
||||
const label = addressProvided
|
||||
? __( 'No available delivery option', 'woocommerce' )
|
||||
: __( 'Enter address to calculate', 'woocommerce' );
|
||||
return (
|
||||
<span className="wc-block-components-shipping-placeholder__value">
|
||||
{ isCheckout
|
||||
? label
|
||||
: __( 'Calculated at checkout', 'woocommerce' ) }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ShippingCalculatorButton
|
||||
label={ __(
|
||||
'Enter address to check delivery options',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingPlaceholder;
|
|
@ -2,22 +2,23 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { useStoreCart } from '@woocommerce/base-context';
|
||||
import { getSelectedShippingRateNames } from '@woocommerce/base-utils';
|
||||
|
||||
export const ShippingVia = ( {
|
||||
selectedShippingRates,
|
||||
}: {
|
||||
selectedShippingRates: string[];
|
||||
} ): JSX.Element => {
|
||||
return (
|
||||
<div className="wc-block-components-totals-item__description wc-block-components-totals-shipping__via">
|
||||
export const ShippingVia = (): JSX.Element | null => {
|
||||
const { shippingRates } = useStoreCart();
|
||||
const rateNames = getSelectedShippingRateNames( shippingRates );
|
||||
return rateNames ? (
|
||||
<div className="wc-block-components-totals-shipping__via">
|
||||
{ decodeEntities(
|
||||
selectedShippingRates
|
||||
rateNames
|
||||
.filter(
|
||||
( item, index ) =>
|
||||
selectedShippingRates.indexOf( item ) === index
|
||||
( item, index ) => rateNames.indexOf( item ) === index
|
||||
)
|
||||
.join( ', ' )
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default ShippingVia;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wc-block-components-totals-shipping__delivery-options-notice,
|
||||
.wc-block-components-shipping-address {
|
||||
margin-top: $gap;
|
||||
display: block;
|
||||
|
@ -54,6 +55,11 @@
|
|||
.wc-block-components-shipping-placeholder__value {
|
||||
@include font-size(small);
|
||||
}
|
||||
|
||||
.wc-block-components-totals-shipping__via {
|
||||
@include font-size(small);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Extra classes for specificity.
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
*/
|
||||
import { screen, render } from '@testing-library/react';
|
||||
import { SlotFillProvider } from '@woocommerce/blocks-checkout';
|
||||
import { previewCart as mockPreviewCart } from '@woocommerce/resource-previews';
|
||||
import { ShippingCalculatorContext } from '@woocommerce/base-components/cart-checkout/shipping-calculator/context';
|
||||
import * as wpData from '@wordpress/data';
|
||||
import { CartShippingRate } from '@woocommerce/types';
|
||||
import { previewCart as mockPreviewCart } from '@woocommerce/resource-previews';
|
||||
import * as baseContextHooks from '@woocommerce/base-context/hooks';
|
||||
|
||||
/**
|
||||
|
@ -51,6 +53,54 @@ const shippingAddress = {
|
|||
phone: '+1234567890',
|
||||
};
|
||||
|
||||
const shippingRates = [
|
||||
{
|
||||
package_id: 0,
|
||||
name: 'Initial Shipment',
|
||||
destination: {
|
||||
address_1: '30 Test Street',
|
||||
address_2: 'Apt 1 Shipping',
|
||||
city: 'Liverpool',
|
||||
state: '',
|
||||
postcode: 'L1 0BP',
|
||||
country: 'GB',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: 'acf4b89d3d503d8252c9c4ba75ddbf6d',
|
||||
name: 'Test product',
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
shipping_rates: [
|
||||
{
|
||||
rate_id: 'flat_rate:1',
|
||||
name: 'Shipping',
|
||||
description: '',
|
||||
delivery_time: '',
|
||||
price: '0',
|
||||
taxes: '0',
|
||||
instance_id: 13,
|
||||
method_id: 'flat_rate',
|
||||
meta_data: [
|
||||
{
|
||||
key: 'Items',
|
||||
value: 'Test product × 1',
|
||||
},
|
||||
],
|
||||
selected: false,
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as CartShippingRate[];
|
||||
|
||||
jest.mock( '@woocommerce/base-context/hooks', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
|
@ -59,117 +109,20 @@ jest.mock( '@woocommerce/base-context/hooks', () => {
|
|||
useStoreCart: jest.fn(),
|
||||
};
|
||||
} );
|
||||
|
||||
baseContextHooks.useShippingData.mockReturnValue( {
|
||||
needsShipping: true,
|
||||
selectShippingRate: jest.fn(),
|
||||
shippingRates: [
|
||||
{
|
||||
package_id: 0,
|
||||
name: 'Shipping method',
|
||||
destination: {
|
||||
address_1: '',
|
||||
address_2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
postcode: '',
|
||||
country: '',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: 'fb0c0a746719a7596f296344b80cb2b6',
|
||||
name: 'Hoodie - Blue, Yes',
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
key: '1f0e3dad99908345f7439f8ffabdffc4',
|
||||
name: 'Beanie',
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
shipping_rates: [
|
||||
{
|
||||
rate_id: 'flat_rate:1',
|
||||
name: 'Flat rate',
|
||||
description: '',
|
||||
delivery_time: '',
|
||||
price: '500',
|
||||
taxes: '0',
|
||||
instance_id: 1,
|
||||
method_id: 'flat_rate',
|
||||
meta_data: [
|
||||
{
|
||||
key: 'Items',
|
||||
value: 'Hoodie - Blue, Yes × 1, Beanie × 1',
|
||||
},
|
||||
],
|
||||
selected: false,
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
},
|
||||
{
|
||||
rate_id: 'local_pickup:2',
|
||||
name: 'Local pickup',
|
||||
description: '',
|
||||
delivery_time: '',
|
||||
price: '0',
|
||||
taxes: '0',
|
||||
instance_id: 2,
|
||||
method_id: 'local_pickup',
|
||||
meta_data: [
|
||||
{
|
||||
key: 'Items',
|
||||
value: 'Hoodie - Blue, Yes × 1, Beanie × 1',
|
||||
},
|
||||
],
|
||||
selected: false,
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
},
|
||||
{
|
||||
rate_id: 'free_shipping:5',
|
||||
name: 'Free shipping',
|
||||
description: '',
|
||||
delivery_time: '',
|
||||
price: '0',
|
||||
taxes: '0',
|
||||
instance_id: 5,
|
||||
method_id: 'free_shipping',
|
||||
meta_data: [
|
||||
{
|
||||
key: 'Items',
|
||||
value: 'Hoodie - Blue, Yes × 1, Beanie × 1',
|
||||
},
|
||||
],
|
||||
selected: true,
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
shippingRates,
|
||||
} );
|
||||
|
||||
baseContextHooks.useStoreCart.mockReturnValue( {
|
||||
cartItems: mockPreviewCart.items,
|
||||
cartTotals: [ mockPreviewCart.totals ],
|
||||
cartTotals: mockPreviewCart.totals,
|
||||
cartCoupons: mockPreviewCart.coupons,
|
||||
cartFees: mockPreviewCart.fees,
|
||||
cartNeedsShipping: mockPreviewCart.needs_shipping,
|
||||
shippingRates: [],
|
||||
shippingRates,
|
||||
shippingAddress,
|
||||
billingAddress: mockPreviewCart.billing_address,
|
||||
cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping,
|
||||
|
@ -179,141 +132,73 @@ baseContextHooks.useStoreCart.mockReturnValue( {
|
|||
describe( 'TotalsShipping', () => {
|
||||
it( 'shows FREE if shipping cost is 0', () => {
|
||||
baseContextHooks.useStoreCart.mockReturnValue( {
|
||||
cartItems: mockPreviewCart.items,
|
||||
cartTotals: [ mockPreviewCart.totals ],
|
||||
cartCoupons: mockPreviewCart.coupons,
|
||||
cartFees: mockPreviewCart.fees,
|
||||
cartNeedsShipping: mockPreviewCart.needs_shipping,
|
||||
...baseContextHooks.useStoreCart(),
|
||||
shippingRates: [
|
||||
{
|
||||
package_id: 0,
|
||||
name: 'Initial Shipment',
|
||||
destination: {
|
||||
address_1: '30 Test Street',
|
||||
address_2: 'Apt 1 Shipping',
|
||||
city: 'Liverpool',
|
||||
state: '',
|
||||
postcode: 'L1 0BP',
|
||||
country: 'GB',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: 'acf4b89d3d503d8252c9c4ba75ddbf6d',
|
||||
name: 'Test product',
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
shipping_rates: [
|
||||
{
|
||||
rate_id: 'free_shipping:1',
|
||||
name: 'Free shipping',
|
||||
description: '',
|
||||
delivery_time: '',
|
||||
price: '0',
|
||||
taxes: '0',
|
||||
instance_id: 13,
|
||||
method_id: 'free_shipping',
|
||||
meta_data: [
|
||||
{
|
||||
key: 'Items',
|
||||
value: 'Test product × 1',
|
||||
},
|
||||
],
|
||||
selected: false,
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
...shippingRates,
|
||||
{ ...shippingRates[ 0 ], price: '0' },
|
||||
],
|
||||
shippingAddress,
|
||||
billingAddress: mockPreviewCart.billing_address,
|
||||
cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping,
|
||||
isLoadingRates: false,
|
||||
cartTotals: {
|
||||
...mockPreviewCart.totals,
|
||||
total_shipping: '0',
|
||||
total_shipping_tax: '0',
|
||||
},
|
||||
} );
|
||||
|
||||
const { rerender } = render(
|
||||
<SlotFillProvider>
|
||||
<TotalsShipping
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '0',
|
||||
total_shipping_tax: '0',
|
||||
} }
|
||||
showCalculator={ true }
|
||||
showRateSelector={ false }
|
||||
isCheckout={ false }
|
||||
className={ '' }
|
||||
/>
|
||||
<TotalsShipping />
|
||||
</SlotFillProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText( 'Free', { exact: true } )
|
||||
).toBeInTheDocument();
|
||||
expect( screen.queryByText( '0.00' ) ).not.toBeInTheDocument();
|
||||
|
||||
baseContextHooks.useStoreCart.mockReturnValue( {
|
||||
...baseContextHooks.useStoreCart(),
|
||||
shippingRates: [
|
||||
...shippingRates,
|
||||
{ ...shippingRates[ 0 ], price: '5678' },
|
||||
],
|
||||
cartTotals: {
|
||||
...mockPreviewCart.totals,
|
||||
total_shipping: '5678',
|
||||
total_shipping_tax: '0',
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_prefix: '',
|
||||
currency_suffix: '',
|
||||
currency_thousand_separator: ', ',
|
||||
},
|
||||
} );
|
||||
|
||||
rerender(
|
||||
<SlotFillProvider>
|
||||
<TotalsShipping
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '5678',
|
||||
total_shipping_tax: '0',
|
||||
} }
|
||||
showCalculator={ true }
|
||||
showRateSelector={ false }
|
||||
isCheckout={ false }
|
||||
className={ '' }
|
||||
/>
|
||||
<TotalsShipping />
|
||||
</SlotFillProvider>
|
||||
);
|
||||
|
||||
expect( screen.queryByText( 'Free' ) ).not.toBeInTheDocument();
|
||||
expect( screen.getByText( '56.78' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should show correct calculator button label if address is complete', () => {
|
||||
render(
|
||||
<SlotFillProvider>
|
||||
<TotalsShipping
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
<ShippingCalculatorContext.Provider
|
||||
value={ {
|
||||
showCalculator: true,
|
||||
isShippingCalculatorOpen: false,
|
||||
setIsShippingCalculatorOpen: jest.fn(),
|
||||
shippingCalculatorID:
|
||||
'shipping-calculator-form-wrapper',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '0',
|
||||
total_shipping_tax: '0',
|
||||
} }
|
||||
showCalculator={ true }
|
||||
showRateSelector={ true }
|
||||
isCheckout={ false }
|
||||
className={ '' }
|
||||
/>
|
||||
>
|
||||
<TotalsShipping />
|
||||
</ShippingCalculatorContext.Provider>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect(
|
||||
|
@ -323,45 +208,31 @@ describe( 'TotalsShipping', () => {
|
|||
).toBeInTheDocument();
|
||||
expect( screen.getByText( 'Change address' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should show correct calculator button label if address is incomplete', () => {
|
||||
baseContextHooks.useStoreCart.mockReturnValue( {
|
||||
cartItems: mockPreviewCart.items,
|
||||
cartTotals: [ mockPreviewCart.totals ],
|
||||
cartCoupons: mockPreviewCart.coupons,
|
||||
cartFees: mockPreviewCart.fees,
|
||||
cartNeedsShipping: mockPreviewCart.needs_shipping,
|
||||
shippingRates: [],
|
||||
...baseContextHooks.useStoreCart(),
|
||||
shippingAddress: {
|
||||
...shippingAddress,
|
||||
city: '',
|
||||
country: '',
|
||||
postcode: '',
|
||||
},
|
||||
billingAddress: mockPreviewCart.billing_address,
|
||||
cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping,
|
||||
isLoadingRates: false,
|
||||
} );
|
||||
|
||||
render(
|
||||
<SlotFillProvider>
|
||||
<TotalsShipping
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
<ShippingCalculatorContext.Provider
|
||||
value={ {
|
||||
showCalculator: true,
|
||||
isShippingCalculatorOpen: false,
|
||||
setIsShippingCalculatorOpen: jest.fn(),
|
||||
shippingCalculatorID:
|
||||
'shipping-calculator-form-wrapper',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '0',
|
||||
total_shipping_tax: '0',
|
||||
} }
|
||||
showCalculator={ true }
|
||||
showRateSelector={ true }
|
||||
isCheckout={ false }
|
||||
className={ '' }
|
||||
/>
|
||||
>
|
||||
<TotalsShipping />
|
||||
</ShippingCalculatorContext.Provider>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect(
|
||||
|
@ -371,14 +242,10 @@ describe( 'TotalsShipping', () => {
|
|||
screen.getByText( 'Enter address to check delivery options' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'does show the calculator button when default rates are available and has formatted address', () => {
|
||||
baseContextHooks.useStoreCart.mockReturnValue( {
|
||||
cartItems: mockPreviewCart.items,
|
||||
cartTotals: [ mockPreviewCart.totals ],
|
||||
cartCoupons: mockPreviewCart.coupons,
|
||||
cartFees: mockPreviewCart.fees,
|
||||
cartNeedsShipping: mockPreviewCart.needs_shipping,
|
||||
shippingRates: mockPreviewCart.shipping_rates,
|
||||
...baseContextHooks.useStoreCart(),
|
||||
shippingAddress: {
|
||||
...shippingAddress,
|
||||
city: '',
|
||||
|
@ -386,31 +253,21 @@ describe( 'TotalsShipping', () => {
|
|||
country: 'US',
|
||||
postcode: '',
|
||||
},
|
||||
billingAddress: mockPreviewCart.billing_address,
|
||||
cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping,
|
||||
isLoadingRates: false,
|
||||
} );
|
||||
|
||||
render(
|
||||
<SlotFillProvider>
|
||||
<TotalsShipping
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
<ShippingCalculatorContext.Provider
|
||||
value={ {
|
||||
showCalculator: true,
|
||||
isShippingCalculatorOpen: false,
|
||||
setIsShippingCalculatorOpen: jest.fn(),
|
||||
shippingCalculatorID:
|
||||
'shipping-calculator-form-wrapper',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '0',
|
||||
total_shipping_tax: '0',
|
||||
} }
|
||||
showCalculator={ true }
|
||||
showRateSelector={ true }
|
||||
isCheckout={ false }
|
||||
className={ '' }
|
||||
/>
|
||||
>
|
||||
<TotalsShipping />
|
||||
</ShippingCalculatorContext.Provider>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect( screen.queryByText( 'Change address' ) ).toBeInTheDocument();
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import ShippingAddress from '@woocommerce/base-components/cart-checkout/totals/shipping/shipping-address';
|
||||
import { CART_STORE_KEY, CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { ShippingCalculatorContext } from '@woocommerce/base-components/cart-checkout';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import * as baseContextHooks from '@woocommerce/base-context/hooks';
|
||||
|
||||
jest.mock( '@woocommerce/settings', () => {
|
||||
const originalModule = jest.requireActual( '@woocommerce/settings' );
|
||||
|
@ -25,35 +27,66 @@ jest.mock( '@woocommerce/settings', () => {
|
|||
},
|
||||
};
|
||||
} );
|
||||
describe( 'ShippingAddress', () => {
|
||||
const testShippingAddress = {
|
||||
first_name: 'John',
|
||||
last_name: 'Doe',
|
||||
company: 'Automattic',
|
||||
address_1: '123 Main St',
|
||||
address_2: '',
|
||||
city: 'San Francisco',
|
||||
state: 'CA',
|
||||
postcode: '94107',
|
||||
country: 'US',
|
||||
phone: '555-555-5555',
|
||||
};
|
||||
|
||||
it( 'renders ShippingLocation if user does not prefer collection', () => {
|
||||
jest.mock( '@woocommerce/base-context/hooks', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
...jest.requireActual( '@woocommerce/base-context/hooks' ),
|
||||
useStoreCart: jest.fn(),
|
||||
};
|
||||
} );
|
||||
|
||||
const shippingAddress = {
|
||||
first_name: 'John',
|
||||
last_name: 'Doe',
|
||||
company: 'Automattic',
|
||||
address_1: '123 Main St',
|
||||
address_2: '',
|
||||
city: 'San Francisco',
|
||||
state: 'CA',
|
||||
postcode: '94107',
|
||||
country: 'US',
|
||||
phone: '555-555-5555',
|
||||
};
|
||||
|
||||
baseContextHooks.useStoreCart.mockReturnValue( {
|
||||
cartItems: previewCart.items,
|
||||
cartTotals: previewCart.totals,
|
||||
cartCoupons: previewCart.coupons,
|
||||
cartFees: previewCart.fees,
|
||||
cartNeedsShipping: previewCart.needs_shipping,
|
||||
shippingRates: previewCart.shipping_rates,
|
||||
shippingAddress,
|
||||
billingAddress: previewCart.billing_address,
|
||||
cartHasCalculatedShipping: previewCart.has_calculated_shipping,
|
||||
isLoadingRates: false,
|
||||
} );
|
||||
|
||||
describe( 'ShippingAddress', () => {
|
||||
it( 'Renders shipping address if user does not prefer collection', () => {
|
||||
render(
|
||||
<ShippingAddress
|
||||
showCalculator={ false }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
shippingAddress={ testShippingAddress }
|
||||
/>
|
||||
<ShippingCalculatorContext.Provider
|
||||
value={ {
|
||||
showCalculator: true,
|
||||
isShippingCalculatorOpen: false,
|
||||
setIsShippingCalculatorOpen: jest.fn(),
|
||||
shippingCalculatorID: 'shipping-calculator-form-wrapper',
|
||||
} }
|
||||
>
|
||||
<ShippingAddress />
|
||||
</ShippingCalculatorContext.Provider>
|
||||
);
|
||||
expect( screen.getByText( /Delivers to 94107/ ) ).toBeInTheDocument();
|
||||
expect( screen.getByText( 'Change address' ) ).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText( /Collection from/ )
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText( 'Enter address to check delivery options' )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
it( 'renders PickupLocation if shopper prefers collection', async () => {
|
||||
|
||||
it( 'Renders pickup location if shopper prefers collection', async () => {
|
||||
dispatch( CHECKOUT_STORE_KEY ).setPrefersCollection( true );
|
||||
|
||||
// Deselect the default selected rate and select pickup_location:1 rate.
|
||||
|
@ -75,12 +108,16 @@ describe( 'ShippingAddress', () => {
|
|||
dispatch( CART_STORE_KEY ).receiveCart( previewCart );
|
||||
|
||||
render(
|
||||
<ShippingAddress
|
||||
showCalculator={ false }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
shippingAddress={ testShippingAddress }
|
||||
/>
|
||||
<ShippingCalculatorContext.Provider
|
||||
value={ {
|
||||
showCalculator: true,
|
||||
isShippingCalculatorOpen: false,
|
||||
setIsShippingCalculatorOpen: jest.fn(),
|
||||
shippingCalculatorID: 'shipping-calculator-form-wrapper',
|
||||
} }
|
||||
>
|
||||
<ShippingAddress />
|
||||
</ShippingCalculatorContext.Provider>
|
||||
);
|
||||
expect(
|
||||
screen.getByText(
|
||||
|
@ -88,4 +125,69 @@ describe( 'ShippingAddress', () => {
|
|||
)
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( `renders an address if one is set in the methods metadata`, async () => {
|
||||
dispatch( CHECKOUT_STORE_KEY ).setPrefersCollection( true );
|
||||
|
||||
// Deselect the default selected rate and select pickup_location:1 rate.
|
||||
const currentlySelectedIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.selected
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
currentlySelectedIndex
|
||||
].selected = false;
|
||||
const pickupRateIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.method_id === 'pickup_location'
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].selected = true;
|
||||
|
||||
dispatch( CART_STORE_KEY ).receiveCart( previewCart );
|
||||
|
||||
render( <ShippingAddress /> );
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Collection from 123 Easy Street, New York, 12345/
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
it( 'renders no address if one is not set in the methods metadata', async () => {
|
||||
dispatch( CHECKOUT_STORE_KEY ).setPrefersCollection( true );
|
||||
|
||||
// Deselect the default selected rate and select pickup_location:1 rate.
|
||||
const currentlySelectedIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.selected
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
currentlySelectedIndex
|
||||
].selected = false;
|
||||
const pickupRateIndex =
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates.findIndex(
|
||||
( rate ) => rate.rate_id === 'pickup_location:2'
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].selected = true;
|
||||
|
||||
// Set the pickup_location metadata value to an empty string in the selected pickup rate.
|
||||
const addressKeyIndex = previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].meta_data.findIndex(
|
||||
( metaData ) => metaData.key === 'pickup_address'
|
||||
);
|
||||
previewCart.shipping_rates[ 0 ].shipping_rates[
|
||||
pickupRateIndex
|
||||
].meta_data[ addressKeyIndex ].value = '';
|
||||
|
||||
dispatch( CART_STORE_KEY ).receiveCart( previewCart );
|
||||
|
||||
render( <ShippingAddress /> );
|
||||
expect(
|
||||
screen.queryByText( /Collection from / )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { screen, render } from '@testing-library/react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ShippingPlaceholder from '../shipping-placeholder';
|
||||
|
||||
const shippingCalculatorID = 'shipping-calculator-form-wrapper';
|
||||
|
||||
describe( 'ShippingPlaceholder', () => {
|
||||
it( 'should show correct text if showCalculator is false and addressProvided is false', () => {
|
||||
const { rerender } = render(
|
||||
<ShippingPlaceholder
|
||||
showCalculator={ false }
|
||||
addressProvided={ false }
|
||||
isCheckout={ true }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
shippingCalculatorID={ shippingCalculatorID }
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByText( 'Enter address to calculate' )
|
||||
).toBeInTheDocument();
|
||||
rerender(
|
||||
<ShippingPlaceholder
|
||||
showCalculator={ false }
|
||||
isCheckout={ false }
|
||||
addressProvided={ false }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
shippingCalculatorID={ shippingCalculatorID }
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByText( 'Calculated at checkout' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should show correct text if showCalculator is false and addressProvided is true', () => {
|
||||
render(
|
||||
<ShippingPlaceholder
|
||||
showCalculator={ false }
|
||||
addressProvided={ true }
|
||||
isCheckout={ true }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
shippingCalculatorID={ shippingCalculatorID }
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByText( 'No available delivery option' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import type { CartResponseShippingRate } from '@woocommerce/type-defs/cart-response';
|
||||
import { hasCollectableRate } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
* Searches an array of packages/rates to see if there are actually any rates
|
||||
* available.
|
||||
*
|
||||
* @param {Array} shippingRatePackages An array of packages and rates.
|
||||
* @return {boolean} True if a rate exists.
|
||||
*/
|
||||
export const hasShippingRate = (
|
||||
shippingRatePackages: CartResponseShippingRate[]
|
||||
): boolean => {
|
||||
return shippingRatePackages.some(
|
||||
( shippingRatePackage ) => shippingRatePackage.shipping_rates.length
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the total shipping value based on store settings.
|
||||
*/
|
||||
export const getTotalShippingValue = ( values: {
|
||||
total_shipping: string;
|
||||
total_shipping_tax: string;
|
||||
} ): number => {
|
||||
return getSetting( 'displayCartPricesIncludingTax', false )
|
||||
? parseInt( values.total_shipping, 10 ) +
|
||||
parseInt( values.total_shipping_tax, 10 )
|
||||
: parseInt( values.total_shipping, 10 );
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if no shipping methods are available or if all available shipping methods are local pickup
|
||||
* only.
|
||||
*/
|
||||
export const areShippingMethodsMissing = (
|
||||
hasRates: boolean,
|
||||
prefersCollection: boolean | undefined,
|
||||
shippingRates: CartResponseShippingRate[]
|
||||
) => {
|
||||
if ( ! hasRates ) {
|
||||
// No shipping methods available
|
||||
return true;
|
||||
}
|
||||
|
||||
// We check for the availability of shipping options if the shopper selected "Shipping"
|
||||
if ( ! prefersCollection ) {
|
||||
return shippingRates.some(
|
||||
( shippingRatePackage ) =>
|
||||
! shippingRatePackage.shipping_rates.some(
|
||||
( shippingRate ) =>
|
||||
! hasCollectableRate( shippingRate.method_id )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
getTotalShippingValue,
|
||||
isPackageRateCollectable,
|
||||
} from '@woocommerce/base-utils';
|
||||
import {
|
||||
isObject,
|
||||
objectHasProp,
|
||||
CartShippingRate,
|
||||
CartResponseTotals,
|
||||
} from '@woocommerce/types';
|
||||
|
||||
export const renderShippingTotalValue = ( values: CartResponseTotals ) => {
|
||||
const totalShippingValue = getTotalShippingValue( values );
|
||||
if ( totalShippingValue === 0 ) {
|
||||
return <strong>{ __( 'Free', 'woocommerce' ) }</strong>;
|
||||
}
|
||||
return totalShippingValue;
|
||||
};
|
||||
|
||||
export const getPickupLocation = (
|
||||
shippingRates: CartShippingRate[]
|
||||
): string => {
|
||||
const flattenedRates = ( shippingRates || [] ).flatMap(
|
||||
( shippingRate ) => shippingRate.shipping_rates
|
||||
);
|
||||
|
||||
const selectedCollectableRate = flattenedRates.find(
|
||||
( rate ) => rate.selected && isPackageRateCollectable( rate )
|
||||
);
|
||||
|
||||
// If the rate has an address specified in its metadata.
|
||||
if (
|
||||
isObject( selectedCollectableRate ) &&
|
||||
objectHasProp( selectedCollectableRate, 'meta_data' )
|
||||
) {
|
||||
const selectedRateMetaData = selectedCollectableRate.meta_data.find(
|
||||
( meta ) => meta.key === 'pickup_address'
|
||||
);
|
||||
if (
|
||||
isObject( selectedRateMetaData ) &&
|
||||
objectHasProp( selectedRateMetaData, 'value' ) &&
|
||||
selectedRateMetaData.value
|
||||
) {
|
||||
return selectedRateMetaData.value;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
import {
|
||||
defaultFields,
|
||||
AddressFields,
|
||||
FormFields,
|
||||
ShippingAddress,
|
||||
BillingAddress,
|
||||
getSetting,
|
||||
|
@ -30,7 +30,7 @@ interface CheckoutAddress {
|
|||
setUseShippingAsBilling: ( useShippingAsBilling: boolean ) => void;
|
||||
setEditingBillingAddress: ( isEditing: boolean ) => void;
|
||||
setEditingShippingAddress: ( isEditing: boolean ) => void;
|
||||
defaultFields: AddressFields;
|
||||
defaultFields: FormFields;
|
||||
showShippingFields: boolean;
|
||||
showBillingFields: boolean;
|
||||
forcedBillingAddress: boolean;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useDispatch, useSelect } from '@wordpress/data';
|
|||
import { CART_STORE_KEY, CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { hasShippingRate } from '@woocommerce/base-components/cart-checkout/totals/shipping/utils';
|
||||
import { hasShippingRate } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
CartShippingPackageShippingRate,
|
||||
CartShippingRate,
|
||||
} from '@woocommerce/type-defs/cart';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { LOCAL_PICKUP_ENABLED } from '@woocommerce/block-settings';
|
||||
import type {
|
||||
CartShippingPackageShippingRate,
|
||||
CartShippingRate,
|
||||
} from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Get the number of packages in a shippingRates array.
|
||||
|
@ -59,3 +59,97 @@ export const getShippingRatesRateCount = (
|
|||
return count + shippingPackage.shipping_rates.length;
|
||||
}, 0 );
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches an array of packages/rates to see if there are actually any rates
|
||||
* available.
|
||||
*
|
||||
* @param {Array} shippingRates An array of packages and rates.
|
||||
* @return {boolean} True if a rate exists.
|
||||
*/
|
||||
export const hasShippingRate = (
|
||||
shippingRates: CartShippingRate[]
|
||||
): boolean => {
|
||||
return shippingRates.some(
|
||||
( shippingRatesPackage ) =>
|
||||
!! shippingRatesPackage.shipping_rates.length
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters an array of packages/rates based on the shopper's preference for collection.
|
||||
*/
|
||||
export const filterShippingRatesByPrefersCollection = (
|
||||
shippingRates: CartShippingRate[],
|
||||
prefersCollection: boolean
|
||||
) => {
|
||||
return shippingRates.map( ( shippingRatesPackage ) => {
|
||||
return {
|
||||
...shippingRatesPackage,
|
||||
shipping_rates: shippingRatesPackage.shipping_rates.filter(
|
||||
( rate ) => {
|
||||
const collectableRate = hasCollectableRate(
|
||||
rate.method_id
|
||||
);
|
||||
|
||||
if ( prefersCollection ) {
|
||||
return collectableRate;
|
||||
}
|
||||
|
||||
return ! collectableRate;
|
||||
}
|
||||
),
|
||||
};
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the total shipping value based on store settings.
|
||||
*/
|
||||
export const getTotalShippingValue = ( values: {
|
||||
total_shipping: string;
|
||||
total_shipping_tax: string;
|
||||
} ): number => {
|
||||
return getSetting( 'displayCartPricesIncludingTax', false )
|
||||
? parseInt( values.total_shipping, 10 ) +
|
||||
parseInt( values.total_shipping_tax, 10 )
|
||||
: parseInt( values.total_shipping, 10 );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the names of the selected rates in an array of shipping rates.
|
||||
*/
|
||||
export const getSelectedShippingRateNames = (
|
||||
shippingRates: CartShippingRate[]
|
||||
): string[] => {
|
||||
return shippingRates.flatMap( ( shippingPackage ) => {
|
||||
return shippingPackage.shipping_rates
|
||||
.filter( ( rate ) => rate.selected )
|
||||
.flatMap( ( rate ) => rate.name );
|
||||
} );
|
||||
};
|
||||
|
||||
export const selectedRatesAreCollectable = (
|
||||
shippingRates: CartShippingRate[]
|
||||
): boolean => {
|
||||
return hasShippingRate( shippingRates )
|
||||
? shippingRates.every( ( shippingPackage ) => {
|
||||
return shippingPackage.shipping_rates.every(
|
||||
( rate ) =>
|
||||
! rate.selected || isPackageRateCollectable( rate )
|
||||
);
|
||||
} )
|
||||
: false;
|
||||
};
|
||||
|
||||
export const allRatesAreCollectable = (
|
||||
shippingRates: CartShippingRate[]
|
||||
): boolean => {
|
||||
return hasShippingRate( shippingRates )
|
||||
? shippingRates.every( ( shippingPackage ) => {
|
||||
return shippingPackage.shipping_rates.every( ( rate ) =>
|
||||
isPackageRateCollectable( rate )
|
||||
);
|
||||
} )
|
||||
: false;
|
||||
};
|
||||
|
|
|
@ -17,10 +17,12 @@ import './style.scss';
|
|||
|
||||
/**
|
||||
* PaymentMethods component.
|
||||
*
|
||||
* @return {*} The rendered component.
|
||||
*/
|
||||
const PaymentMethods = () => {
|
||||
const PaymentMethods = ( {
|
||||
noPaymentMethods = <NoPaymentMethods />,
|
||||
}: {
|
||||
noPaymentMethods?: JSX.Element | undefined;
|
||||
} ) => {
|
||||
const [ showPaymentMethodsToggle, setShowPaymentMethodsToggle ] =
|
||||
useState( false );
|
||||
const {
|
||||
|
@ -50,7 +52,7 @@ const PaymentMethods = () => {
|
|||
paymentMethodsInitialized &&
|
||||
Object.keys( availablePaymentMethods ).length === 0
|
||||
) {
|
||||
return <NoPaymentMethods />;
|
||||
return noPaymentMethods;
|
||||
}
|
||||
|
||||
// Show payment methods if the toggle is on or if there are no saved payment methods, or if the active saved token is not set.
|
|
@ -8,8 +8,6 @@ import {
|
|||
} from '@wordpress/block-editor';
|
||||
import { addFilter, hasFilter } from '@wordpress/hooks';
|
||||
import type { StoreDescriptor } from '@wordpress/data';
|
||||
import { NoPaymentMethodsNotice } from '@woocommerce/editor-components/no-payment-methods-notice';
|
||||
import { PAYMENT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { DefaultNotice } from '@woocommerce/editor-components/default-notice';
|
||||
import { IncompatibleExtensionsNotice } from '@woocommerce/editor-components/incompatible-extension-notice';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
|
@ -35,13 +33,7 @@ const withSidebarNotices = createHigherOrderComponent(
|
|||
isSelected: isBlockSelected,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
isCart,
|
||||
isCheckout,
|
||||
isPaymentMethodsBlock,
|
||||
hasPaymentMethods,
|
||||
parentId,
|
||||
} = useSelect( ( select ) => {
|
||||
const { isCart, isCheckout, parentId } = useSelect( ( select ) => {
|
||||
const { getBlockParentsByBlockName, getBlockName } =
|
||||
select( blockEditorStore );
|
||||
|
||||
|
@ -82,13 +74,6 @@ const withSidebarNotices = createHigherOrderComponent(
|
|||
currentBlockName === targetParentBlock
|
||||
? clientId
|
||||
: parents[ targetParentBlock ],
|
||||
isPaymentMethodsBlock:
|
||||
currentBlockName === 'woocommerce/checkout-payment-block',
|
||||
hasPaymentMethods:
|
||||
select( PAYMENT_STORE_KEY ).paymentMethodsInitialized() &&
|
||||
Object.keys(
|
||||
select( PAYMENT_STORE_KEY ).getAvailablePaymentMethods()
|
||||
).length > 0,
|
||||
};
|
||||
} );
|
||||
|
||||
|
@ -112,11 +97,6 @@ const withSidebarNotices = createHigherOrderComponent(
|
|||
/>
|
||||
|
||||
<DefaultNotice block={ isCheckout ? 'checkout' : 'cart' } />
|
||||
|
||||
{ isPaymentMethodsBlock && ! hasPaymentMethods && (
|
||||
<NoPaymentMethodsNotice />
|
||||
) }
|
||||
|
||||
<CartCheckoutFeedbackPrompt />
|
||||
</InspectorControls>
|
||||
<BlockEdit key="edit" { ...props } />
|
||||
|
|
|
@ -1,43 +1,104 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { TotalsShipping } from '@woocommerce/base-components/cart-checkout';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import { useStoreCart, useEditorContext } from '@woocommerce/base-context/';
|
||||
import { TotalsWrapper } from '@woocommerce/blocks-components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import {
|
||||
TotalsShipping,
|
||||
ShippingCalculatorButton,
|
||||
ShippingCalculator,
|
||||
} from '@woocommerce/base-components/cart-checkout';
|
||||
import { ShippingCalculatorContext } from '@woocommerce/base-components/cart-checkout/shipping-calculator/context';
|
||||
import { useEditorContext, useStoreCart } from '@woocommerce/base-context';
|
||||
import { TotalsWrapper } from '@woocommerce/blocks-checkout';
|
||||
import {
|
||||
getShippingRatesPackageCount,
|
||||
selectedRatesAreCollectable,
|
||||
allRatesAreCollectable,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { getShippingRatesPackageCount } from '@woocommerce/base-utils';
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ShippingRateSelector } from './shipping-rate-selector';
|
||||
|
||||
const Block = ( { className }: { className: string } ): JSX.Element | null => {
|
||||
const { cartTotals, cartNeedsShipping } = useStoreCart();
|
||||
const { isEditor } = useEditorContext();
|
||||
const { cartNeedsShipping, shippingRates } = useStoreCart();
|
||||
const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] =
|
||||
useState( false );
|
||||
|
||||
if ( ! cartNeedsShipping ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shippingRates = select( 'wc/store/cart' ).getShippingRates();
|
||||
const shippingRatesPackageCount =
|
||||
getShippingRatesPackageCount( shippingRates );
|
||||
|
||||
if ( ! shippingRatesPackageCount && isEditor ) {
|
||||
if ( isEditor && getShippingRatesPackageCount( shippingRates ) === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||
const showCalculator = getSetting< boolean >(
|
||||
'isShippingCalculatorEnabled',
|
||||
true
|
||||
);
|
||||
|
||||
const hasSelectedCollectionOnly =
|
||||
selectedRatesAreCollectable( shippingRates );
|
||||
|
||||
return (
|
||||
<TotalsWrapper className={ className }>
|
||||
<TotalsShipping
|
||||
showCalculator={ getSetting< boolean >(
|
||||
'isShippingCalculatorEnabled',
|
||||
true
|
||||
) }
|
||||
showRateSelector={ true }
|
||||
values={ cartTotals }
|
||||
currency={ totalsCurrency }
|
||||
/>
|
||||
<ShippingCalculatorContext.Provider
|
||||
value={ {
|
||||
showCalculator,
|
||||
shippingCalculatorID: 'shipping-calculator-form-wrapper',
|
||||
isShippingCalculatorOpen,
|
||||
setIsShippingCalculatorOpen,
|
||||
} }
|
||||
>
|
||||
<TotalsShipping
|
||||
label={
|
||||
hasSelectedCollectionOnly
|
||||
? __( 'Collection', 'woocommerce' )
|
||||
: __( 'Delivery', 'woocommerce' )
|
||||
}
|
||||
placeholder={
|
||||
showCalculator ? (
|
||||
<ShippingCalculatorButton
|
||||
label={ __(
|
||||
'Enter address to check delivery options',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
) : (
|
||||
<span className="wc-block-components-shipping-placeholder__value">
|
||||
{ __(
|
||||
'Calculated on checkout',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
collaterals={
|
||||
<>
|
||||
{ isShippingCalculatorOpen && (
|
||||
<ShippingCalculator />
|
||||
) }
|
||||
{ ! isShippingCalculatorOpen && (
|
||||
<ShippingRateSelector />
|
||||
) }
|
||||
{ ! showCalculator &&
|
||||
allRatesAreCollectable( shippingRates ) && (
|
||||
<div className="wc-block-components-totals-shipping__delivery-options-notice">
|
||||
{ __(
|
||||
'Delivery options will be calculated during checkout',
|
||||
'woocommerce'
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ShippingCalculatorContext.Provider>
|
||||
</TotalsWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,45 +2,35 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import type {
|
||||
CartResponseShippingAddress,
|
||||
CartResponseShippingRate,
|
||||
} from '@woocommerce/types';
|
||||
import NoticeBanner from '@woocommerce/base-components/notice-banner';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ShippingRatesControl from '../../shipping-rates-control';
|
||||
import { formatShippingAddress } from '../../../../utils';
|
||||
|
||||
export interface ShippingRateSelectorProps {
|
||||
hasRates: boolean;
|
||||
shippingRates: CartResponseShippingRate[];
|
||||
shippingAddress: CartResponseShippingAddress;
|
||||
isLoadingRates: boolean;
|
||||
isAddressComplete: boolean;
|
||||
}
|
||||
|
||||
export const ShippingRateSelector = ( {
|
||||
hasRates,
|
||||
shippingRates,
|
||||
shippingAddress,
|
||||
isLoadingRates,
|
||||
import { ShippingRatesControl } from '@woocommerce/base-components/cart-checkout';
|
||||
import NoticeBanner from '@woocommerce/base-components/notice-banner';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
formatShippingAddress,
|
||||
isAddressComplete,
|
||||
}: ShippingRateSelectorProps ): JSX.Element => {
|
||||
const legend = hasRates
|
||||
? __( 'Shipping options', 'woocommerce' )
|
||||
: __( 'Choose a shipping option', 'woocommerce' );
|
||||
} from '@woocommerce/base-utils';
|
||||
|
||||
export const ShippingRateSelector = (): JSX.Element => {
|
||||
const { shippingRates, isLoadingRates, shippingAddress } = useStoreCart();
|
||||
|
||||
const hasCompleteAddress = isAddressComplete( shippingAddress, [
|
||||
'state',
|
||||
'country',
|
||||
'postcode',
|
||||
'city',
|
||||
] );
|
||||
|
||||
return (
|
||||
<fieldset className="wc-block-components-totals-shipping__fieldset">
|
||||
<legend className="screen-reader-text">{ legend }</legend>
|
||||
<legend className="screen-reader-text">
|
||||
{ __( 'Shipping options', 'woocommerce' ) }
|
||||
</legend>
|
||||
<ShippingRatesControl
|
||||
className="wc-block-components-totals-shipping__options"
|
||||
noResultsMessage={
|
||||
<>
|
||||
{ isAddressComplete && (
|
||||
{ hasCompleteAddress && (
|
||||
<NoticeBanner
|
||||
isDismissible={ false }
|
||||
className="wc-block-components-shipping-rates-control__no-results-notice"
|
|
@ -146,11 +146,9 @@
|
|||
margin-top: 0;
|
||||
|
||||
.wc-block-components-shipping-calculator,
|
||||
.wc-block-components-shipping-rates-control__package:not(
|
||||
.wc-block-components-panel
|
||||
) {
|
||||
padding-left: $gap;
|
||||
padding-right: $gap;
|
||||
.wc-block-components-shipping-rates-control__package {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.wc-block-components-totals-item__description.wc-block-components-totals-shipping__via,
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Placeholder, Button } from '@wordpress/components';
|
||||
import { Icon, blockDefault } from '@wordpress/icons';
|
||||
import PALETTE from '@automattic/color-studio';
|
||||
|
||||
/**
|
||||
* Placeholder component that links to a settings page to configure something.
|
||||
*/
|
||||
const ConfigurePlaceholder = ( {
|
||||
label,
|
||||
description,
|
||||
buttonLabel,
|
||||
buttonHref,
|
||||
icon = blockDefault,
|
||||
}: {
|
||||
label: string;
|
||||
description: string;
|
||||
buttonLabel: string;
|
||||
buttonHref: string;
|
||||
icon?: Icon;
|
||||
} ) => {
|
||||
return (
|
||||
<Placeholder
|
||||
icon={ <Icon icon={ icon } /> }
|
||||
label={ label }
|
||||
className="wc-block-checkout__configure-placeholder"
|
||||
>
|
||||
<span className="wc-block-checkout__configure-placeholder-description">
|
||||
{ description }
|
||||
</span>
|
||||
<Button
|
||||
variant="primary"
|
||||
href={ buttonHref }
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={ {
|
||||
backgroundColor: PALETTE.colors[ 'Gray 100' ],
|
||||
color: PALETTE.colors.White,
|
||||
pointerEvents: 'all',
|
||||
} }
|
||||
>
|
||||
{ buttonLabel }
|
||||
</Button>
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigurePlaceholder;
|
|
@ -1,4 +1,4 @@
|
|||
.components-placeholder.wc-block-checkout__no-shipping-placeholder {
|
||||
.components-placeholder.wc-block-checkout__configure-placeholder {
|
||||
margin-bottom: $gap;
|
||||
|
||||
* {
|
||||
|
@ -13,7 +13,7 @@
|
|||
color: $white;
|
||||
}
|
||||
|
||||
.wc-block-checkout__no-shipping-placeholder-description {
|
||||
.wc-block-checkout__configure-placeholder-description {
|
||||
display: block;
|
||||
margin: 0.25em 0 1em 0;
|
||||
}
|
|
@ -16,6 +16,10 @@
|
|||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"disableProductDescriptions": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
|
@ -24,10 +28,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"parent": [
|
||||
"woocommerce/checkout-order-summary-block"
|
||||
],
|
||||
"parent": [ "woocommerce/checkout-order-summary-block" ],
|
||||
"textdomain": "woocommerce",
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,23 @@ import { OrderSummary } from '@woocommerce/base-components/cart-checkout';
|
|||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { TotalsWrapper } from '@woocommerce/blocks-components';
|
||||
|
||||
const Block = ( { className = '' }: { className?: string } ): JSX.Element => {
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { BlockAttributes } from './edit';
|
||||
|
||||
const Block = ( {
|
||||
className = '',
|
||||
disableProductDescriptions = false,
|
||||
}: BlockAttributes ): JSX.Element => {
|
||||
const { cartItems } = useStoreCart();
|
||||
|
||||
return (
|
||||
<TotalsWrapper className={ className }>
|
||||
<OrderSummary cartItems={ cartItems } />
|
||||
<OrderSummary
|
||||
cartItems={ cartItems }
|
||||
disableProductDescriptions={ disableProductDescriptions }
|
||||
/>
|
||||
</TotalsWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,26 +1,61 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
|
||||
export type BlockAttributes = {
|
||||
className: string;
|
||||
disableProductDescriptions: boolean;
|
||||
};
|
||||
|
||||
export const Edit = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
}: {
|
||||
attributes: {
|
||||
className: string;
|
||||
};
|
||||
attributes: BlockAttributes;
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element => {
|
||||
const { className } = attributes;
|
||||
const { className, disableProductDescriptions } = attributes;
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<Block className={ className } />
|
||||
{ /* For now this setting can only be enabled if you have experimental features enabled. */ }
|
||||
{ isExperimentalBlocksEnabled() && (
|
||||
<InspectorControls>
|
||||
<PanelBody title={ __( 'Settings', 'woocommerce' ) }>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Disable product descriptions',
|
||||
'woocommerce'
|
||||
) }
|
||||
help={ __(
|
||||
'Disable display of product descriptions.',
|
||||
'woocommerce'
|
||||
) }
|
||||
checked={ disableProductDescriptions }
|
||||
onChange={ () =>
|
||||
setAttributes( {
|
||||
disableProductDescriptions:
|
||||
! disableProductDescriptions,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
) }
|
||||
<Block
|
||||
disableProductDescriptions={ disableProductDescriptions }
|
||||
className={ className }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,32 +1,68 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { TotalsShipping } from '@woocommerce/base-components/cart-checkout';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { useStoreCart } from '@woocommerce/base-context';
|
||||
import { TotalsWrapper } from '@woocommerce/blocks-checkout';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
|
||||
import {
|
||||
filterShippingRatesByPrefersCollection,
|
||||
isAddressComplete,
|
||||
selectedRatesAreCollectable,
|
||||
} from '@woocommerce/base-utils';
|
||||
|
||||
const Block = ( {
|
||||
className = '',
|
||||
}: {
|
||||
className?: string;
|
||||
} ): JSX.Element | null => {
|
||||
const { cartTotals, cartNeedsShipping } = useStoreCart();
|
||||
const { cartNeedsShipping, shippingRates, shippingAddress } =
|
||||
useStoreCart();
|
||||
const prefersCollection = useSelect( ( select ) =>
|
||||
select( CHECKOUT_STORE_KEY ).prefersCollection()
|
||||
);
|
||||
|
||||
if ( ! cartNeedsShipping ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||
const hasSelectedCollectionOnly = selectedRatesAreCollectable(
|
||||
filterShippingRatesByPrefersCollection(
|
||||
shippingRates,
|
||||
prefersCollection ?? false
|
||||
)
|
||||
);
|
||||
|
||||
const hasCompleteAddress = isAddressComplete( shippingAddress, [
|
||||
'state',
|
||||
'country',
|
||||
'postcode',
|
||||
'city',
|
||||
] );
|
||||
|
||||
return (
|
||||
<TotalsWrapper className={ className }>
|
||||
<TotalsShipping
|
||||
showCalculator={ false }
|
||||
showRateSelector={ false }
|
||||
values={ cartTotals }
|
||||
currency={ totalsCurrency }
|
||||
isCheckout={ true }
|
||||
label={
|
||||
hasSelectedCollectionOnly
|
||||
? __( 'Collection', 'woocommerce' )
|
||||
: __( 'Delivery', 'woocommerce' )
|
||||
}
|
||||
placeholder={
|
||||
<span className="wc-block-components-shipping-placeholder__value">
|
||||
{ hasCompleteAddress
|
||||
? __(
|
||||
'No available delivery option',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Enter address to calculate',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</TotalsWrapper>
|
||||
);
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
*/
|
||||
import { PaymentMethods } from '../../../cart-checkout-shared/payment-methods';
|
||||
|
||||
const Block = (): JSX.Element | null => {
|
||||
return <PaymentMethods />;
|
||||
const Block = ( {
|
||||
noPaymentMethods,
|
||||
}: {
|
||||
noPaymentMethods?: JSX.Element | undefined;
|
||||
} ): JSX.Element | null => {
|
||||
return <PaymentMethods noPaymentMethods={ noPaymentMethods } />;
|
||||
};
|
||||
|
||||
export default Block;
|
||||
|
|
|
@ -5,6 +5,7 @@ import clsx from 'clsx';
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { PanelBody, ExternalLink } from '@wordpress/components';
|
||||
import { payment } from '@wordpress/icons';
|
||||
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
|
@ -24,6 +25,7 @@ import {
|
|||
AdditionalFieldsContent,
|
||||
} from '../../form-step';
|
||||
import Block from './block';
|
||||
import ConfigurePlaceholder from '../../configure-placeholder';
|
||||
|
||||
export const Edit = ( {
|
||||
attributes,
|
||||
|
@ -119,7 +121,23 @@ export const Edit = ( {
|
|||
) }
|
||||
</InspectorControls>
|
||||
<Noninteractive>
|
||||
<Block />
|
||||
<Block
|
||||
noPaymentMethods={
|
||||
<ConfigurePlaceholder
|
||||
icon={ payment }
|
||||
label={ __( 'Payment options', 'woocommerce' ) }
|
||||
description={ __(
|
||||
'Your store does not have any payment methods that support the Checkout block. Once you have configured a compatible payment method it will be displayed here.',
|
||||
'woocommerce'
|
||||
) }
|
||||
buttonLabel={ __(
|
||||
'Configure Payment Options',
|
||||
'woocommerce'
|
||||
) }
|
||||
buttonHref={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=checkout` }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Noninteractive>
|
||||
<AdditionalFields block={ innerBlockAreas.PAYMENT_METHODS } />
|
||||
</FormStepBlock>
|
||||
|
|
|
@ -60,7 +60,26 @@ const renderShippingRatesControlOption = (
|
|||
};
|
||||
};
|
||||
|
||||
const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
|
||||
const NoShippingAddressMessage = () => {
|
||||
return (
|
||||
<p
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="wc-block-components-shipping-rates-control__no-shipping-address-message"
|
||||
>
|
||||
{ __(
|
||||
'Enter a shipping address to view shipping options.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const Block = ( {
|
||||
noShippingPlaceholder = null,
|
||||
}: {
|
||||
noShippingPlaceholder?: ReactElement | null;
|
||||
} ) => {
|
||||
const { isEditor } = useEditorContext();
|
||||
|
||||
const {
|
||||
|
@ -95,14 +114,7 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
|
|||
getShippingRatesPackageCount( shippingRates );
|
||||
|
||||
if ( ! hasCalculatedShipping && ! shippingRatesPackageCount ) {
|
||||
return (
|
||||
<p>
|
||||
{ __(
|
||||
'Shipping options will be displayed here after entering your full shipping address.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
);
|
||||
return <NoShippingAddressMessage />;
|
||||
}
|
||||
const addressComplete = isAddressComplete( shippingAddress );
|
||||
|
||||
|
@ -124,15 +136,12 @@ const Block = ( { noShippingPlaceholder = null } ): ReactElement | null => {
|
|||
status="warning"
|
||||
>
|
||||
{ __(
|
||||
'There are no shipping options available. Please check your shipping address.',
|
||||
'No shipping options are available for this address. Please verify the address is correct or try a different address.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</NoticeBanner>
|
||||
) : (
|
||||
__(
|
||||
'Add a shipping address to view shipping options.',
|
||||
'woocommerce'
|
||||
)
|
||||
<NoShippingAddressMessage />
|
||||
) }
|
||||
</>
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import clsx from 'clsx';
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { PanelBody, ExternalLink } from '@wordpress/components';
|
||||
import { shipping } from '@wordpress/icons';
|
||||
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
|
@ -19,7 +20,7 @@ import {
|
|||
AdditionalFields,
|
||||
AdditionalFieldsContent,
|
||||
} from '../../form-step';
|
||||
import NoShippingPlaceholder from './no-shipping-placeholder';
|
||||
import ConfigurePlaceholder from '../../configure-placeholder';
|
||||
import Block from './block';
|
||||
import './editor.scss';
|
||||
|
||||
|
@ -126,7 +127,23 @@ export const Edit = ( {
|
|||
) }
|
||||
</InspectorControls>
|
||||
<Noninteractive>
|
||||
<Block noShippingPlaceholder={ <NoShippingPlaceholder /> } />
|
||||
<Block
|
||||
noShippingPlaceholder={
|
||||
<ConfigurePlaceholder
|
||||
icon={ shipping }
|
||||
label={ __( 'Shipping options', 'woocommerce' ) }
|
||||
description={ __(
|
||||
'Your store does not have any Shipping Options configured. Once you have added your Shipping Options they will appear here.',
|
||||
'woocommerce'
|
||||
) }
|
||||
buttonLabel={ __(
|
||||
'Configure Shipping Options',
|
||||
'woocommerce'
|
||||
) }
|
||||
buttonHref={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=shipping` }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Noninteractive>
|
||||
<AdditionalFields block={ innerBlockAreas.SHIPPING_METHODS } />
|
||||
</FormStepBlock>
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Placeholder, Button } from '@wordpress/components';
|
||||
import { Icon, shipping } from '@wordpress/icons';
|
||||
import { ADMIN_URL } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const NoShippingPlaceholder = () => {
|
||||
return (
|
||||
<Placeholder
|
||||
icon={ <Icon icon={ shipping } /> }
|
||||
label={ __( 'Shipping options', 'woocommerce' ) }
|
||||
className="wc-block-checkout__no-shipping-placeholder"
|
||||
>
|
||||
<span className="wc-block-checkout__no-shipping-placeholder-description">
|
||||
{ __(
|
||||
'Your store does not have any Shipping Options configured. Once you have added your Shipping Options they will appear here.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=shipping` }
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{ __( 'Configure Shipping Options', 'woocommerce' ) }
|
||||
</Button>
|
||||
</Placeholder>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoShippingPlaceholder;
|
|
@ -11,4 +11,11 @@
|
|||
.wc-block-components-shipping-rates-control__no-results-notice {
|
||||
margin: em($gap-small) 0;
|
||||
}
|
||||
|
||||
.wc-block-components-shipping-rates-control__no-shipping-address-message {
|
||||
background-color: $gray-200;
|
||||
color: $gray-700;
|
||||
padding: em($gap-large);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,12 @@ import {
|
|||
PanelBody,
|
||||
ToggleControl,
|
||||
Placeholder,
|
||||
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
|
||||
// @ts-expect-error - no types.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
} from '@wordpress/components';
|
||||
|
@ -72,6 +76,7 @@ const ProductCategoriesBlock = ( {
|
|||
>
|
||||
<ToggleGroupControl
|
||||
label={ __( 'Display style', 'woocommerce' ) }
|
||||
isBlock
|
||||
value={ isDropdown ? 'dropdown' : 'list' }
|
||||
onChange={ ( value: string ) =>
|
||||
setAttributes( {
|
||||
|
|
|
@ -10,7 +10,9 @@ import Block from './block';
|
|||
import './editor.scss';
|
||||
import type { ProductCategoriesBlockProps } from './types';
|
||||
|
||||
export const Edit = ( props: ProductCategoriesBlockProps ): JSX.Element => {
|
||||
export default function ProductCategoriesEdit(
|
||||
props: ProductCategoriesBlockProps
|
||||
): JSX.Element {
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
return (
|
||||
|
@ -18,4 +20,4 @@ export const Edit = ( props: ProductCategoriesBlockProps ): JSX.Element => {
|
|||
<Block { ...props } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ import { Icon, listView } from '@wordpress/icons';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './editor.scss';
|
||||
import metadata from './block.json';
|
||||
import './style.scss';
|
||||
import { Edit } from './edit';
|
||||
import Edit from './edit';
|
||||
import type { ProductCategoriesIndexProps } from './types';
|
||||
import './editor.scss';
|
||||
import './style.scss';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
|
|
|
@ -9,6 +9,8 @@ import { BlockEditProps } from '@wordpress/blocks';
|
|||
import { Disabled, Tooltip } from '@wordpress/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { isSiteEditorPage } from '@woocommerce/utils';
|
||||
import { getSettingWithCoercion } from '@woocommerce/settings';
|
||||
import { isBoolean } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -26,8 +28,15 @@ export interface Attributes {
|
|||
const Edit = ( props: BlockEditProps< Attributes > ) => {
|
||||
const { setAttributes } = props;
|
||||
|
||||
const isStepperLayoutFeatureEnabled = getSettingWithCoercion(
|
||||
'isStepperLayoutFeatureEnabled',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
|
||||
const quantitySelectorStyleClass =
|
||||
props.attributes.quantitySelectorStyle === QuantitySelectorStyle.Input
|
||||
props.attributes.quantitySelectorStyle ===
|
||||
QuantitySelectorStyle.Input || ! isStepperLayoutFeatureEnabled
|
||||
? 'wc-block-add-to-cart-form--input'
|
||||
: 'wc-block-add-to-cart-form--stepper';
|
||||
|
||||
|
@ -52,10 +61,14 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Settings
|
||||
quantitySelectorStyle={ props.attributes.quantitySelectorStyle }
|
||||
setAttributes={ setAttributes }
|
||||
/>
|
||||
{ isStepperLayoutFeatureEnabled && (
|
||||
<Settings
|
||||
quantitySelectorStyle={
|
||||
props.attributes.quantitySelectorStyle
|
||||
}
|
||||
setAttributes={ setAttributes }
|
||||
/>
|
||||
) }
|
||||
<div { ...blockProps }>
|
||||
<Tooltip
|
||||
text="Customer will see product add-to-cart options in this space, dependent on the product type. "
|
||||
|
@ -64,8 +77,9 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
|
|||
<div className="wc-block-editor-add-to-cart-form-container">
|
||||
<Skeleton numberOfLines={ 3 } />
|
||||
<Disabled>
|
||||
{ props.attributes.quantitySelectorStyle ===
|
||||
QuantitySelectorStyle.Input && (
|
||||
{ ( props.attributes.quantitySelectorStyle ===
|
||||
QuantitySelectorStyle.Input ||
|
||||
! isStepperLayoutFeatureEnabled ) && (
|
||||
<>
|
||||
<div className="quantity">
|
||||
<input
|
||||
|
@ -98,44 +112,52 @@ const Edit = ( props: BlockEditProps< Attributes > ) => {
|
|||
</>
|
||||
) }
|
||||
{ props.attributes.quantitySelectorStyle ===
|
||||
QuantitySelectorStyle.Stepper && (
|
||||
<>
|
||||
<div className="quantity wc-block-components-quantity-selector">
|
||||
<button className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">
|
||||
-
|
||||
QuantitySelectorStyle.Stepper &&
|
||||
isStepperLayoutFeatureEnabled && (
|
||||
<>
|
||||
<div className="quantity wc-block-components-quantity-selector">
|
||||
<button className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">
|
||||
-
|
||||
</button>
|
||||
<input
|
||||
style={
|
||||
// In the post editor, the editor isn't in an iframe, so WordPress styles are applied. We need to remove them.
|
||||
! isSiteEditor
|
||||
? {
|
||||
backgroundColor:
|
||||
'#ffffff',
|
||||
lineHeight:
|
||||
'normal',
|
||||
minHeight:
|
||||
'unset',
|
||||
boxSizing:
|
||||
'unset',
|
||||
borderRadius:
|
||||
'unset',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
type={ 'number' }
|
||||
value={ '1' }
|
||||
className={
|
||||
'input-text qty text'
|
||||
}
|
||||
readOnly
|
||||
/>
|
||||
<button className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className={ `single_add_to_cart_button alt wp-element-button` }
|
||||
>
|
||||
{ __(
|
||||
'Add to cart',
|
||||
'woocommerce'
|
||||
) }
|
||||
</button>
|
||||
<input
|
||||
style={
|
||||
// In the post editor, the editor isn't in an iframe, so WordPress styles are applied. We need to remove them.
|
||||
! isSiteEditor
|
||||
? {
|
||||
backgroundColor:
|
||||
'#ffffff',
|
||||
lineHeight:
|
||||
'normal',
|
||||
minHeight: 'unset',
|
||||
boxSizing: 'unset',
|
||||
borderRadius:
|
||||
'unset',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
type={ 'number' }
|
||||
value={ '1' }
|
||||
className={ 'input-text qty text' }
|
||||
readOnly
|
||||
/>
|
||||
<button className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className={ `single_add_to_cart_button alt wp-element-button` }
|
||||
>
|
||||
{ __( 'Add to cart', 'woocommerce' ) }
|
||||
</button>
|
||||
</>
|
||||
) }
|
||||
</>
|
||||
) }
|
||||
</Disabled>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { BlockVariation } from '@wordpress/blocks';
|
||||
import { Icon, button } from '@wordpress/icons';
|
||||
|
||||
const variations: BlockVariation[] = [
|
||||
{
|
||||
name: 'product-filters-overlay-navigation-open-trigger',
|
||||
title: __( 'Overlay Navigation (Experimental)', 'woocommerce' ),
|
||||
attributes: {
|
||||
triggerType: 'open-overlay',
|
||||
},
|
||||
isDefault: false,
|
||||
icon: <Icon icon={ button } />,
|
||||
isActive: [ 'triggerType' ],
|
||||
},
|
||||
];
|
||||
|
||||
export const blockVariations = variations;
|
|
@ -1,106 +0,0 @@
|
|||
{
|
||||
"name": "woocommerce/product-filters-overlay-navigation",
|
||||
"title": "Overlay Navigation (Experimental)",
|
||||
"description": "Display overlay navigation controls.",
|
||||
"category": "woocommerce",
|
||||
"keywords": [ "WooCommerce" ],
|
||||
"textdomain": "woocommerce",
|
||||
"ancestor": [ "woocommerce/product-filters-overlay", "woocommerce/product-filters" ],
|
||||
"attributes": {
|
||||
"align": {
|
||||
"type": "string",
|
||||
"default": "right"
|
||||
},
|
||||
"navigationStyle": {
|
||||
"type": "string",
|
||||
"default": "label-and-icon"
|
||||
},
|
||||
"buttonStyle": {
|
||||
"type": "string",
|
||||
"default": "link"
|
||||
},
|
||||
"iconSize": {
|
||||
"type": "number"
|
||||
},
|
||||
"overlayMode": {
|
||||
"type": "string",
|
||||
"default": "never"
|
||||
},
|
||||
"overlayIcon": {
|
||||
"type": "string",
|
||||
"default": "filter-icon-1"
|
||||
},
|
||||
"style": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"spacing": {
|
||||
"blockGap": "1rem"
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggerType": {
|
||||
"type": "string",
|
||||
"default": "close-overlay"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"interactivity": true,
|
||||
"align": [ "left", "right", "center"],
|
||||
"inserter": true,
|
||||
"color": {
|
||||
"__experimentalDefaultControls": {
|
||||
"text": false,
|
||||
"background": false
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"sticky": true
|
||||
},
|
||||
"typography": {
|
||||
"fontSize": true,
|
||||
"lineHeight": true,
|
||||
"__experimentalFontWeight": true,
|
||||
"__experimentalFontFamily": true,
|
||||
"__experimentalFontStyle": true,
|
||||
"__experimentalTextTransform": true,
|
||||
"__experimentalTextDecoration": true,
|
||||
"__experimentalLetterSpacing": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"fontSize": false
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"default": {
|
||||
"type": "flex",
|
||||
"orientation": "horizontal",
|
||||
"flexWrap": "nowrap"
|
||||
},
|
||||
"allowEditing": false
|
||||
},
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true,
|
||||
"blockGap": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"margin": false,
|
||||
"padding": false,
|
||||
"blockGap": false
|
||||
}
|
||||
},
|
||||
"__experimentalBorder": {
|
||||
"color": true,
|
||||
"radius": true,
|
||||
"style": true,
|
||||
"width": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"color": false,
|
||||
"radius": false,
|
||||
"style": false,
|
||||
"width": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"usesContext": [ "woocommerce/product-filters/overlay"],
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
|
||||
import { BlockEditProps, store as blocksStore } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import clsx from 'clsx';
|
||||
import { Icon, close, menu, settings } from '@wordpress/icons';
|
||||
import { filter, filterThreeLines } from '@woocommerce/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { BlockAttributes, BlockVariationTriggerType } from './types';
|
||||
import './editor.scss';
|
||||
import { Inspector } from './inspector-controls';
|
||||
|
||||
const OverlayNavigationLabel = ( {
|
||||
variation,
|
||||
}: {
|
||||
variation: BlockVariationTriggerType;
|
||||
} ) => {
|
||||
let label = __( 'Close', 'woocommerce' );
|
||||
if ( variation === 'open-overlay' ) {
|
||||
label = __( 'Filters', 'woocommerce' );
|
||||
}
|
||||
|
||||
return <span>{ label }</span>;
|
||||
};
|
||||
|
||||
const OverlayNavigationIcon = ( {
|
||||
variation,
|
||||
iconSize,
|
||||
overlayIcon,
|
||||
style,
|
||||
}: {
|
||||
variation: BlockVariationTriggerType;
|
||||
iconSize: number | undefined;
|
||||
overlayIcon: string;
|
||||
style: BlockAttributes[ 'style' ];
|
||||
} ) => {
|
||||
let icon = close;
|
||||
|
||||
if ( variation === 'open-overlay' ) {
|
||||
switch ( overlayIcon ) {
|
||||
case 'filter-icon-4':
|
||||
icon = settings;
|
||||
break;
|
||||
case 'filter-icon-3':
|
||||
icon = menu;
|
||||
break;
|
||||
case 'filter-icon-2':
|
||||
icon = filterThreeLines;
|
||||
break;
|
||||
case 'filter-icon-1':
|
||||
icon = filter;
|
||||
break;
|
||||
default:
|
||||
icon = filter;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Icon
|
||||
fill="currentColor"
|
||||
icon={ icon }
|
||||
style={ {
|
||||
width: iconSize || style?.typography?.fontSize || '16px',
|
||||
height: iconSize || style?.typography?.fontSize || '16px',
|
||||
} }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const OverlayNavigationContent = ( {
|
||||
variation,
|
||||
iconSize,
|
||||
style,
|
||||
overlayIcon,
|
||||
navigationStyle,
|
||||
}: {
|
||||
variation: BlockVariationTriggerType;
|
||||
iconSize: BlockAttributes[ 'iconSize' ];
|
||||
style: BlockAttributes[ 'style' ];
|
||||
overlayIcon: BlockAttributes[ 'overlayIcon' ];
|
||||
navigationStyle: BlockAttributes[ 'navigationStyle' ];
|
||||
} ) => {
|
||||
const overlayNavigationLabel = (
|
||||
<OverlayNavigationLabel variation={ variation } />
|
||||
);
|
||||
const overlayNavigationIcon = (
|
||||
<OverlayNavigationIcon
|
||||
variation={ variation }
|
||||
iconSize={ iconSize }
|
||||
overlayIcon={ overlayIcon }
|
||||
style={ style }
|
||||
/>
|
||||
);
|
||||
|
||||
if ( navigationStyle === 'label-and-icon' ) {
|
||||
if ( variation === 'open-overlay' ) {
|
||||
return (
|
||||
<>
|
||||
{ overlayNavigationIcon }
|
||||
{ overlayNavigationLabel }
|
||||
</>
|
||||
);
|
||||
} else if ( variation === 'close-overlay' ) {
|
||||
return (
|
||||
<>
|
||||
{ overlayNavigationLabel }
|
||||
{ overlayNavigationIcon }
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else if ( navigationStyle === 'label-only' ) {
|
||||
return overlayNavigationLabel;
|
||||
} else if ( navigationStyle === 'icon-only' ) {
|
||||
return overlayNavigationIcon;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
type BlockProps = BlockEditProps< BlockAttributes >;
|
||||
|
||||
export const Edit = ( { attributes, setAttributes }: BlockProps ) => {
|
||||
const {
|
||||
navigationStyle,
|
||||
buttonStyle,
|
||||
iconSize,
|
||||
overlayIcon,
|
||||
style,
|
||||
triggerType,
|
||||
} = attributes;
|
||||
|
||||
const blockProps = useBlockProps( {
|
||||
className: clsx( 'wc-block-product-filters-overlay-navigation', {
|
||||
'wp-block-button__link wp-element-button': buttonStyle !== 'link',
|
||||
} ),
|
||||
} );
|
||||
|
||||
const shouldHideBlock = () => {
|
||||
if ( triggerType === 'open-overlay' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
// We need useInnerBlocksProps because Gutenberg only applies layout classes
|
||||
// to parent block. We don't have any inner blocks but we want to use the
|
||||
// layout controls.
|
||||
const innerBlocksProps = useInnerBlocksProps( blockProps );
|
||||
|
||||
const buttonBlockStyles = useSelect(
|
||||
( select ) => select( blocksStore ).getBlockStyles( 'core/button' ),
|
||||
[]
|
||||
);
|
||||
|
||||
const buttonStyles = [
|
||||
{ value: 'link', label: __( 'Link', 'woocommerce' ) },
|
||||
];
|
||||
|
||||
buttonBlockStyles.forEach(
|
||||
( buttonBlockStyle: { name: string; label: string } ) => {
|
||||
if ( buttonBlockStyle.name === 'link' ) return;
|
||||
buttonStyles.push( {
|
||||
value: buttonBlockStyle.name,
|
||||
label: buttonBlockStyle.label,
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
if ( shouldHideBlock() ) {
|
||||
return (
|
||||
<Inspector
|
||||
attributes={ attributes }
|
||||
setAttributes={ setAttributes }
|
||||
buttonStyles={ buttonStyles }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={ clsx(
|
||||
'wc-block-product-filters-overlay-navigation__wrapper',
|
||||
`is-style-${ buttonStyle }`,
|
||||
{
|
||||
'wp-block-button': buttonStyle !== 'link',
|
||||
}
|
||||
) }
|
||||
>
|
||||
<div { ...innerBlocksProps }>
|
||||
<OverlayNavigationContent
|
||||
variation={ triggerType }
|
||||
iconSize={ iconSize }
|
||||
navigationStyle={ navigationStyle }
|
||||
overlayIcon={ overlayIcon }
|
||||
style={ style }
|
||||
/>
|
||||
</div>
|
||||
<Inspector
|
||||
attributes={ attributes }
|
||||
setAttributes={ setAttributes }
|
||||
buttonStyles={ buttonStyles }
|
||||
/>
|
||||
</nav>
|
||||
);
|
||||
};
|
|
@ -1,33 +0,0 @@
|
|||
.wc-block-product-filters-overlay-navigation__icon-size-control {
|
||||
|
||||
.components-range-control__wrapper {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.components-range-control__number {
|
||||
order: 0;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 20px;
|
||||
|
||||
.components-input-control__input::-webkit-outer-spin-button,
|
||||
.components-input-control__input::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Remove stepper arrows in Firefox */
|
||||
.components-input-control__input {
|
||||
appearance: textfield;
|
||||
width: 40px !important;
|
||||
}
|
||||
|
||||
.components-input-control__container::after {
|
||||
content: "px";
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 7px;
|
||||
font-size: 12px;
|
||||
color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { store } from '@woocommerce/interactivity';
|
||||
|
||||
export interface ProductFiltersContext {
|
||||
isDialogOpen: boolean;
|
||||
}
|
||||
|
||||
const productFiltersOverlayNavigation = {
|
||||
state: {},
|
||||
actions: {},
|
||||
callbacks: {},
|
||||
};
|
||||
|
||||
store( 'woocommerce/product-filters', productFiltersOverlayNavigation );
|
||||
|
||||
export type ProductFiltersOverlayNavigation =
|
||||
typeof productFiltersOverlayNavigation;
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { closeSquareShadow } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
import { Edit } from './edit';
|
||||
import { Save } from './save';
|
||||
import { blockVariations } from './block-variations';
|
||||
import './style.scss';
|
||||
|
||||
if ( isExperimentalBlocksEnabled() ) {
|
||||
registerBlockType( metadata, {
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
icon: <Icon icon={ closeSquareShadow } />,
|
||||
variations: blockVariations,
|
||||
} );
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import { BlockEditProps } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { filter, filterThreeLines } from '@woocommerce/icons';
|
||||
import { Icon, menu, settings } from '@wordpress/icons';
|
||||
import {
|
||||
PanelBody,
|
||||
RadioControl,
|
||||
SelectControl,
|
||||
RangeControl,
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { BlockAttributes } from './types';
|
||||
|
||||
interface ButtonStyle {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface InspectorProps {
|
||||
attributes: BlockEditProps< BlockAttributes >[ 'attributes' ];
|
||||
setAttributes: BlockEditProps< BlockAttributes >[ 'setAttributes' ];
|
||||
buttonStyles: ButtonStyle[];
|
||||
}
|
||||
|
||||
export const Inspector = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
buttonStyles,
|
||||
}: InspectorProps ) => {
|
||||
const { navigationStyle, buttonStyle, iconSize, overlayIcon, triggerType } =
|
||||
attributes;
|
||||
return (
|
||||
<InspectorControls group="styles">
|
||||
<PanelBody title={ __( 'Style', 'woocommerce' ) }>
|
||||
<RadioControl
|
||||
selected={ navigationStyle }
|
||||
options={ [
|
||||
{
|
||||
label: __( 'Label and icon', 'woocommerce' ),
|
||||
value: 'label-and-icon',
|
||||
},
|
||||
{
|
||||
label: __( 'Label only', 'woocommerce' ),
|
||||
value: 'label-only',
|
||||
},
|
||||
{
|
||||
label: __( 'Icon only', 'woocommerce' ),
|
||||
value: 'icon-only',
|
||||
},
|
||||
] }
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'navigationStyle' ]
|
||||
) =>
|
||||
setAttributes( {
|
||||
navigationStyle: value,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
|
||||
{ buttonStyles.length <= 3 && (
|
||||
<ToggleGroupControl
|
||||
label={ __( 'Button', 'woocommerce' ) }
|
||||
value={ buttonStyle }
|
||||
isBlock
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'buttonStyle' ]
|
||||
) =>
|
||||
setAttributes( {
|
||||
buttonStyle: value,
|
||||
} )
|
||||
}
|
||||
>
|
||||
{ buttonStyles.map( ( option ) => (
|
||||
<ToggleGroupControlOption
|
||||
key={ option.value }
|
||||
label={ option.label }
|
||||
value={ option.value }
|
||||
/>
|
||||
) ) }
|
||||
</ToggleGroupControl>
|
||||
) }
|
||||
{ buttonStyles.length > 3 && (
|
||||
<SelectControl
|
||||
label={ __( 'Button', 'woocommerce' ) }
|
||||
value={ buttonStyle }
|
||||
options={ buttonStyles }
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'buttonStyle' ]
|
||||
) =>
|
||||
setAttributes( {
|
||||
buttonStyle: value,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
) }
|
||||
|
||||
{ triggerType === 'open-overlay' &&
|
||||
navigationStyle !== 'label-only' && (
|
||||
<ToggleGroupControl
|
||||
label={ __( 'Icon', 'woocommerce' ) }
|
||||
className="wc-block-editor-product-filters__overlay-button-toggle"
|
||||
isBlock={ true }
|
||||
value={ overlayIcon }
|
||||
onChange={ (
|
||||
value: BlockAttributes[ 'overlayIcon' ]
|
||||
) => {
|
||||
setAttributes( {
|
||||
overlayIcon: value,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-1' }
|
||||
aria-label={ __(
|
||||
'Filter icon 1',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={ <Icon size={ 32 } icon={ filter } /> }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-2' }
|
||||
aria-label={ __(
|
||||
'Filter icon 2',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={
|
||||
<Icon
|
||||
size={ 32 }
|
||||
icon={ filterThreeLines }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-3' }
|
||||
aria-label={ __(
|
||||
'Filter icon 3',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={ <Icon size={ 32 } icon={ menu } /> }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'filter-icon-4' }
|
||||
aria-label={ __(
|
||||
'Filter icon 4',
|
||||
'woocommerce'
|
||||
) }
|
||||
label={ <Icon size={ 32 } icon={ settings } /> }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
) }
|
||||
|
||||
{ navigationStyle !== 'label-only' && (
|
||||
<RangeControl
|
||||
className="wc-block-product-filters-overlay-navigation__icon-size-control"
|
||||
label={ __( 'Icon Size', 'woocommerce' ) }
|
||||
value={ iconSize }
|
||||
onChange={ ( newSize: number ) => {
|
||||
setAttributes( { iconSize: newSize } );
|
||||
} }
|
||||
min={ 0 }
|
||||
max={ 300 }
|
||||
/>
|
||||
) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
);
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
const blockProps = useBlockProps.save( {
|
||||
className: clsx( 'wc-block-product-filters-overlay-navigation' ),
|
||||
} );
|
||||
return <div { ...blockProps } />;
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
.wc-block-product-filters-overlay-navigation__wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.wc-block-product-filters-overlay-navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
|
||||
&.alignright {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
&.alignleft {
|
||||
justify-content: unset;
|
||||
}
|
||||
|
||||
&.aligncenter {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
type BorderRadius = {
|
||||
bottomLeft: string;
|
||||
bottomRight: string;
|
||||
topLeft: string;
|
||||
topRight: string;
|
||||
};
|
||||
type BorderSide = {
|
||||
color: string;
|
||||
width: string;
|
||||
};
|
||||
|
||||
export type BlockVariationTriggerType = 'open-overlay' | 'close-overlay';
|
||||
|
||||
export type BlockAttributes = {
|
||||
navigationStyle: 'label-and-icon' | 'label-only' | 'icon-only';
|
||||
buttonStyle: string;
|
||||
iconSize?: number;
|
||||
triggerType: BlockVariationTriggerType;
|
||||
overlayIcon: string;
|
||||
style: {
|
||||
border?: {
|
||||
radius?: string | BorderRadius;
|
||||
width?: string;
|
||||
top?: BorderSide;
|
||||
bottom?: BorderSide;
|
||||
left?: BorderSide;
|
||||
right?: BorderSide;
|
||||
};
|
||||
spacing?: {
|
||||
blockGap?: string;
|
||||
margin?: {
|
||||
top?: string;
|
||||
right?: string;
|
||||
bottom?: string;
|
||||
left?: string;
|
||||
};
|
||||
padding?: {
|
||||
top?: string;
|
||||
right?: string;
|
||||
bottom?: string;
|
||||
left?: string;
|
||||
};
|
||||
};
|
||||
typography?: {
|
||||
fontSize?: string;
|
||||
lineHeight?: number;
|
||||
fontStyle?: string;
|
||||
fontWeight?: string;
|
||||
letterSpacing?: string;
|
||||
textDecoration?: string;
|
||||
textTransform?: string;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,53 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"name": "woocommerce/product-filters-overlay",
|
||||
"version": "1.0.0",
|
||||
"title": "Product Filters Overlay (Experimental)",
|
||||
"description": "Display product filters in an overlay on top of a page.",
|
||||
"category": "woocommerce",
|
||||
"keywords": [
|
||||
"WooCommerce"
|
||||
],
|
||||
"supports": {
|
||||
"align": true,
|
||||
"multiple": false,
|
||||
"inserter": false,
|
||||
"layout": {
|
||||
"allowCustomContentAndWideSize": true
|
||||
},
|
||||
"color": {},
|
||||
"typography": {},
|
||||
"dimensions": {},
|
||||
"spacing": {
|
||||
"padding": true,
|
||||
"blockGap": true
|
||||
}
|
||||
},
|
||||
"textdomain": "woocommerce",
|
||||
"providesContext": {},
|
||||
"attributes": {
|
||||
"overlayStyle": {
|
||||
"type": "string",
|
||||
"default": "drawer"
|
||||
},
|
||||
"overlayPosition": {
|
||||
"type": "string",
|
||||
"default": "left"
|
||||
},
|
||||
"style": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"spacing": {
|
||||
"padding": {
|
||||
"top": "1rem",
|
||||
"right": "1rem",
|
||||
"bottom": "1rem",
|
||||
"left": "1rem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
/* eslint-disable @wordpress/no-unsafe-wp-apis */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
InnerBlocks,
|
||||
useBlockProps,
|
||||
useInnerBlocksProps,
|
||||
InspectorControls,
|
||||
} from '@wordpress/block-editor';
|
||||
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
|
||||
import {
|
||||
PanelBody,
|
||||
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
||||
__experimentalToggleGroupControl as ToggleGroupControl,
|
||||
} from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { ProductFiltersOverlayBlockAttributes } from './types';
|
||||
|
||||
const TEMPLATE: InnerBlockTemplate[] = [ [ 'woocommerce/product-filters' ] ];
|
||||
|
||||
export const Edit = ( {
|
||||
setAttributes,
|
||||
attributes,
|
||||
}: BlockEditProps< ProductFiltersOverlayBlockAttributes > ) => {
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<InspectorControls group="styles">
|
||||
<PanelBody title={ __( 'Style', 'woocommerce' ) }>
|
||||
<ToggleGroupControl
|
||||
className="wc-block-editor-product-filters-overlay__overlay-style-toggle"
|
||||
isBlock={ true }
|
||||
value={ attributes.overlayStyle }
|
||||
onChange={ ( value: 'fullscreen' | 'drawer' ) => {
|
||||
setAttributes( {
|
||||
overlayStyle: value,
|
||||
} );
|
||||
} }
|
||||
help={
|
||||
attributes.overlayStyle === 'fullscreen'
|
||||
? __(
|
||||
'The overlay will fill up the whole screen.',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'The overlay will show on the left or right side of the screen (only on desktop).',
|
||||
'woocommerce'
|
||||
)
|
||||
}
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'fullscreen' }
|
||||
label={ __( 'Full-Screen', 'woocommerce' ) }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'drawer' }
|
||||
label={ __( 'Drawer', 'woocommerce' ) }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
{ attributes.overlayStyle === 'drawer' && (
|
||||
<ToggleGroupControl
|
||||
className="wc-block-editor-product-filters-overlay__overlay-position-toggle"
|
||||
isBlock={ true }
|
||||
value={ attributes.overlayPosition }
|
||||
label={ __( 'POSITION', 'woocommerce' ) }
|
||||
onChange={ ( value: 'left' | 'right' ) => {
|
||||
setAttributes( {
|
||||
overlayPosition: value,
|
||||
} );
|
||||
} }
|
||||
>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'left' }
|
||||
label={ __( 'Left', 'woocommerce' ) }
|
||||
/>
|
||||
<ToggleGroupControlOption
|
||||
value={ 'right' }
|
||||
label={ __( 'Right', 'woocommerce' ) }
|
||||
/>
|
||||
</ToggleGroupControl>
|
||||
) }
|
||||
|
||||
{ attributes.overlayStyle === 'drawer' ? (
|
||||
<img
|
||||
className="wc-block-editor-product-filters-overlay__drawer-image"
|
||||
src={
|
||||
attributes.overlayPosition === 'left'
|
||||
? `${ WC_BLOCKS_IMAGE_URL }blocks/product-filters-overlay/overlay-drawer-left.svg`
|
||||
: `${ WC_BLOCKS_IMAGE_URL }blocks/product-filters-overlay/overlay-drawer-right.svg`
|
||||
}
|
||||
alt={ __(
|
||||
'Overlay drawer orientation',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="wc-block-editor-product-filters-overlay__drawer-image"
|
||||
src={ `${ WC_BLOCKS_IMAGE_URL }blocks/product-filters-overlay/overlay-drawer-fullscreen.svg` }
|
||||
alt={ __(
|
||||
'Overlay drawer orientation',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<InnerBlocks templateLock={ false } template={ TEMPLATE } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = () => {
|
||||
const blockProps = useBlockProps.save();
|
||||
const innerBlocksProps = useInnerBlocksProps.save( blockProps );
|
||||
return <div { ...innerBlocksProps } />;
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, pages } from '@wordpress/icons';
|
||||
|
||||
const icon = () => <Icon icon={ pages } />;
|
||||
|
||||
export default icon;
|
|
@ -1,15 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
import { ProductFiltersOverlayBlockSettings } from './settings';
|
||||
|
||||
if ( isExperimentalBlocksEnabled() ) {
|
||||
registerBlockType( metadata, ProductFiltersOverlayBlockSettings );
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
const blockProps = useBlockProps.save( {
|
||||
className: clsx( 'wc-block-product-filters-overlay' ),
|
||||
} );
|
||||
const innerBlocksProps = useInnerBlocksProps.save( blockProps );
|
||||
return <div { ...innerBlocksProps } />;
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit } from './edit';
|
||||
import { Save } from './save';
|
||||
import icon from './icon';
|
||||
|
||||
export const ProductFiltersOverlayBlockSettings = {
|
||||
icon,
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
export interface ProductFiltersOverlayBlockAttributes {
|
||||
overlayStyle: string;
|
||||
overlayPosition: string;
|
||||
setAttributes: ( attributes: ProductFiltersOverlayBlockAttributes ) => void;
|
||||
}
|
|
@ -32,6 +32,7 @@ import { EXCLUDED_BLOCKS } from '../../constants';
|
|||
import { Notice } from '../../components/notice';
|
||||
import type { Attributes } from './types';
|
||||
import './style.scss';
|
||||
import { InitialDisabled } from '../../components/initial-disabled';
|
||||
|
||||
const RatingFilterEdit = ( props: BlockEditProps< Attributes > ) => {
|
||||
const { attributes, setAttributes } = props;
|
||||
|
@ -189,30 +190,32 @@ const RatingFilterEdit = ( props: BlockEditProps< Attributes > ) => {
|
|||
/>
|
||||
|
||||
<div { ...innerBlocksProps }>
|
||||
{ showNoProductsNotice && (
|
||||
<Notice>
|
||||
{ __(
|
||||
"Your store doesn't have any products with ratings yet. This filter option will display when a product receives a review.",
|
||||
'woocommerce'
|
||||
) }
|
||||
</Notice>
|
||||
) }
|
||||
<div
|
||||
className={ clsx( {
|
||||
'is-loading': isLoading,
|
||||
} ) }
|
||||
>
|
||||
<BlockContextProvider
|
||||
value={ {
|
||||
filterData: {
|
||||
items: displayedOptions,
|
||||
isLoading,
|
||||
},
|
||||
} }
|
||||
<InitialDisabled>
|
||||
{ showNoProductsNotice && (
|
||||
<Notice>
|
||||
{ __(
|
||||
"Your store doesn't have any products with ratings yet. This filter option will display when a product receives a review.",
|
||||
'woocommerce'
|
||||
) }
|
||||
</Notice>
|
||||
) }
|
||||
<div
|
||||
className={ clsx( {
|
||||
'is-loading': isLoading,
|
||||
} ) }
|
||||
>
|
||||
{ children }
|
||||
</BlockContextProvider>
|
||||
</div>
|
||||
<BlockContextProvider
|
||||
value={ {
|
||||
filterData: {
|
||||
items: displayedOptions,
|
||||
isLoading,
|
||||
},
|
||||
} }
|
||||
>
|
||||
{ children }
|
||||
</BlockContextProvider>
|
||||
</div>
|
||||
</InitialDisabled>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
.wp-block-woocommerce-product-filter-rating {
|
||||
display: grid;
|
||||
|
||||
.wc-block-components-product-rating {
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.wc-blocks-no-payment-methods-notice {
|
||||
margin: 0;
|
||||
|
||||
.components-notice__content {
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Notice, ExternalLink } from '@wordpress/components';
|
||||
import { ADMIN_URL } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './editor.scss';
|
||||
|
||||
export function NoPaymentMethodsNotice() {
|
||||
const noticeContent = __(
|
||||
'Your store does not have any payment methods that support the Checkout block. Once you have configured a compatible payment method it will be displayed here.',
|
||||
'woocommerce'
|
||||
);
|
||||
|
||||
return (
|
||||
<Notice
|
||||
className="wc-blocks-no-payment-methods-notice"
|
||||
status={ 'warning' }
|
||||
spokenMessage={ noticeContent }
|
||||
isDismissible={ false }
|
||||
>
|
||||
<div className="wc-blocks-no-payment-methods-notice__content">
|
||||
{ noticeContent }{ ' ' }
|
||||
<ExternalLink
|
||||
href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=checkout` }
|
||||
>
|
||||
{ __( 'Configure Payment Methods', 'woocommerce' ) }
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</Notice>
|
||||
);
|
||||
}
|
|
@ -91,14 +91,6 @@ const blocks = {
|
|||
'product-filters': {
|
||||
isExperimental: true,
|
||||
},
|
||||
'product-filters-overlay': {
|
||||
isExperimental: true,
|
||||
customDir: 'product-filters/inner-blocks/overlay',
|
||||
},
|
||||
'product-filters-overlay-navigation': {
|
||||
isExperimental: true,
|
||||
customDir: 'product-filters/inner-blocks/overlay-navigation',
|
||||
},
|
||||
'product-filter-status': {
|
||||
isExperimental: true,
|
||||
customDir: 'product-filters/inner-blocks/status-filter',
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: WooCommerce Blocks Test Enable Experimental Features
|
||||
* Description: Enable experimental features for WooCommerce Blocks that are behind feature flags.
|
||||
* Plugin URI: https://github.com/woocommerce/woocommerce
|
||||
* Author: WooCommerce
|
||||
*
|
||||
* @package woocommerce-blocks-test-enable-experimental-features
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable experimental features
|
||||
*
|
||||
* @param array $features Array of feature slugs.
|
||||
*/
|
||||
function enable_experimental_features( $features ) {
|
||||
// add experimental block features
|
||||
return array_merge( $features, array( 'experimental-blocks' ) );
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_admin_features', 'enable_experimental_features', 20, 1 );
|
|
@ -191,130 +191,138 @@ test.describe( `${ blockData.name } Block`, () => {
|
|||
).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'has the stepper option visible', async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
test.describe( 'Stepper Layout', () => {
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.setFeatureFlag(
|
||||
'add-to-cart-with-options-stepper-layout',
|
||||
true
|
||||
);
|
||||
} );
|
||||
test( 'has the stepper option visible', async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
|
||||
await blockUtils.configureSingleProductBlock();
|
||||
await blockUtils.configureSingleProductBlock();
|
||||
|
||||
await blockUtils.enableStepperMode();
|
||||
await blockUtils.enableStepperMode();
|
||||
|
||||
const minusButton = editor.canvas.locator(
|
||||
blockData.selectors.editor.stepperMinusButton
|
||||
);
|
||||
const plusButton = editor.canvas.locator(
|
||||
blockData.selectors.editor.stepperPlusButton
|
||||
);
|
||||
const minusButton = editor.canvas.locator(
|
||||
blockData.selectors.editor.stepperMinusButton
|
||||
);
|
||||
const plusButton = editor.canvas.locator(
|
||||
blockData.selectors.editor.stepperPlusButton
|
||||
);
|
||||
|
||||
await expect( minusButton ).toBeVisible();
|
||||
await expect( plusButton ).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'has the stepper mode working on the frontend', async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
page,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
|
||||
const productName = 'Hoodie with Logo';
|
||||
|
||||
await blockUtils.configureSingleProductBlock( productName );
|
||||
|
||||
await blockUtils.enableStepperMode();
|
||||
|
||||
await editor.publishAndVisitPost();
|
||||
|
||||
const minusButton = page.getByLabel( `Reduce quantity` );
|
||||
const plusButton = page.getByLabel( `Increase quantity` );
|
||||
|
||||
await expect( minusButton ).toBeVisible();
|
||||
await expect( plusButton ).toBeVisible();
|
||||
|
||||
const input = page.getByLabel( 'Product quantity' );
|
||||
|
||||
await expect( input ).toHaveValue( '1' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '2' );
|
||||
await minusButton.click();
|
||||
await expect( input ).toHaveValue( '1' );
|
||||
// Ensure the quantity doesn't go below 1.
|
||||
await minusButton.click();
|
||||
await expect( input ).toHaveValue( '1' );
|
||||
} );
|
||||
|
||||
test( "doesn't render stepper when the product is sold individually", async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
page,
|
||||
} ) => {
|
||||
await blockUtils.createSoldIndividuallyProduct();
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
|
||||
const productName = 'Sold Individually';
|
||||
|
||||
await blockUtils.configureSingleProductBlock( productName );
|
||||
await blockUtils.enableStepperMode();
|
||||
|
||||
await editor.publishAndVisitPost();
|
||||
|
||||
const minusButton = page.getByLabel( `Reduce quantity` );
|
||||
const plusButton = page.getByLabel( `Increase quantity ` );
|
||||
|
||||
await expect( minusButton ).toBeHidden();
|
||||
await expect( plusButton ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( 'has the stepper mode working on the frontend with min, max, and step attributes', async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
page,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
|
||||
const productName = 'Hoodie with Logo';
|
||||
|
||||
await blockUtils.configureSingleProductBlock( productName );
|
||||
|
||||
await blockUtils.enableStepperMode();
|
||||
await editor.publishAndVisitPost();
|
||||
|
||||
await blockUtils.setMinMaxAndStep( {
|
||||
min: 2,
|
||||
max: 10,
|
||||
step: 2,
|
||||
await expect( minusButton ).toBeVisible();
|
||||
await expect( plusButton ).toBeVisible();
|
||||
} );
|
||||
|
||||
const minusButton = page.getByLabel( `Reduce quantity` );
|
||||
const plusButton = page.getByLabel( `Increase quantity` );
|
||||
test( 'has the stepper mode working on the frontend', async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
page,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
|
||||
await expect( minusButton ).toBeVisible();
|
||||
await expect( plusButton ).toBeVisible();
|
||||
const productName = 'Hoodie with Logo';
|
||||
|
||||
const input = page.getByLabel( 'Product quantity' );
|
||||
await blockUtils.configureSingleProductBlock( productName );
|
||||
|
||||
await expect( input ).toHaveValue( '2' );
|
||||
await minusButton.click();
|
||||
await expect( input ).toHaveValue( '2' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '4' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '6' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '8' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '10' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '10' );
|
||||
await blockUtils.enableStepperMode();
|
||||
|
||||
await editor.publishAndVisitPost();
|
||||
|
||||
const minusButton = page.getByLabel( `Reduce quantity` );
|
||||
const plusButton = page.getByLabel( `Increase quantity` );
|
||||
|
||||
await expect( minusButton ).toBeVisible();
|
||||
await expect( plusButton ).toBeVisible();
|
||||
|
||||
const input = page.getByLabel( 'Product quantity' );
|
||||
|
||||
await expect( input ).toHaveValue( '1' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '2' );
|
||||
await minusButton.click();
|
||||
await expect( input ).toHaveValue( '1' );
|
||||
// Ensure the quantity doesn't go below 1.
|
||||
await minusButton.click();
|
||||
await expect( input ).toHaveValue( '1' );
|
||||
} );
|
||||
|
||||
test( "doesn't render stepper when the product is sold individually", async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
page,
|
||||
} ) => {
|
||||
await blockUtils.createSoldIndividuallyProduct();
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
|
||||
const productName = 'Sold Individually';
|
||||
|
||||
await blockUtils.configureSingleProductBlock( productName );
|
||||
await blockUtils.enableStepperMode();
|
||||
|
||||
await editor.publishAndVisitPost();
|
||||
|
||||
const minusButton = page.getByLabel( `Reduce quantity` );
|
||||
const plusButton = page.getByLabel( `Increase quantity ` );
|
||||
|
||||
await expect( minusButton ).toBeHidden();
|
||||
await expect( plusButton ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( 'has the stepper mode working on the frontend with min, max, and step attributes', async ( {
|
||||
admin,
|
||||
editor,
|
||||
blockUtils,
|
||||
page,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await editor.insertBlock( { name: 'woocommerce/single-product' } );
|
||||
|
||||
const productName = 'Hoodie with Logo';
|
||||
|
||||
await blockUtils.configureSingleProductBlock( productName );
|
||||
|
||||
await blockUtils.enableStepperMode();
|
||||
await editor.publishAndVisitPost();
|
||||
|
||||
await blockUtils.setMinMaxAndStep( {
|
||||
min: 2,
|
||||
max: 10,
|
||||
step: 2,
|
||||
} );
|
||||
|
||||
const minusButton = page.getByLabel( `Reduce quantity` );
|
||||
const plusButton = page.getByLabel( `Increase quantity` );
|
||||
|
||||
await expect( minusButton ).toBeVisible();
|
||||
await expect( plusButton ).toBeVisible();
|
||||
|
||||
const input = page.getByLabel( 'Product quantity' );
|
||||
|
||||
await expect( input ).toHaveValue( '2' );
|
||||
await minusButton.click();
|
||||
await expect( input ).toHaveValue( '2' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '4' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '6' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '8' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '10' );
|
||||
await plusButton.click();
|
||||
await expect( input ).toHaveValue( '10' );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -78,7 +78,7 @@ test.describe( 'Shopper → Shipping', () => {
|
|||
|
||||
await expect(
|
||||
userPage.getByText(
|
||||
'Shipping options will be displayed here after entering your full shipping address'
|
||||
'Enter a shipping address to view shipping options.'
|
||||
)
|
||||
).toBeVisible();
|
||||
|
||||
|
@ -86,7 +86,7 @@ test.describe( 'Shopper → Shipping', () => {
|
|||
|
||||
await expect(
|
||||
userPage.getByText(
|
||||
'Shipping options will be displayed here after entering your full shipping address'
|
||||
'Enter a shipping address to view shipping options.'
|
||||
)
|
||||
).toBeHidden();
|
||||
} );
|
||||
|
|
|
@ -155,9 +155,7 @@ test.describe( 'Shopper (guest) → Order Confirmation → Create Account', () =
|
|||
test.use( { storageState: guestFile } );
|
||||
|
||||
test.beforeEach( async ( { frontendUtils, pageObject, requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await frontendUtils.goToShop();
|
||||
await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME );
|
||||
await frontendUtils.goToCheckout();
|
||||
|
|
|
@ -18,9 +18,7 @@ const blockData = {
|
|||
|
||||
test.describe( `Filters Overlay Navigation`, () => {
|
||||
test.beforeEach( async ( { admin, requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//${ blockData.templateSlug }`,
|
||||
postType: blockData.templateType,
|
||||
|
|
|
@ -14,9 +14,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( {
|
|||
|
||||
test.describe( 'woocommerce/product-filter-active - Frontend', () => {
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
} );
|
||||
|
||||
test( 'Without any filters selected, active block should not be rendered', async ( {
|
||||
|
|
|
@ -32,9 +32,7 @@ const test = base.extend< { pageObject: ProductFiltersPage } >( {
|
|||
|
||||
test.describe( `${ blockData.name }`, () => {
|
||||
test.beforeEach( async ( { admin, requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//${ blockData.slug }`,
|
||||
postType: 'wp_template',
|
||||
|
|
|
@ -24,9 +24,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( {
|
|||
test.describe( 'woocommerce/product-filter-attribute - Frontend', () => {
|
||||
test.describe( 'With default display style', () => {
|
||||
test.beforeEach( async ( { requestUtils, templateCompiler } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await templateCompiler.compile( {
|
||||
attributes: {
|
||||
attributeId: 1,
|
||||
|
@ -139,9 +137,7 @@ test.describe( 'woocommerce/product-filter-attribute - Frontend', () => {
|
|||
|
||||
test.describe( 'With show counts enabled', () => {
|
||||
test.beforeEach( async ( { requestUtils, templateCompiler } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await templateCompiler.compile( {
|
||||
attributes: {
|
||||
attributeId: 1,
|
||||
|
|
|
@ -15,9 +15,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( {
|
|||
test.describe.skip( 'Product Filter: Price Filter Block', () => {
|
||||
test.describe( 'frontend', () => {
|
||||
test.beforeEach( async ( { requestUtils, templateCompiler } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await templateCompiler.compile();
|
||||
} );
|
||||
|
||||
|
|
|
@ -47,9 +47,7 @@ const test = base.extend< { pageObject: ProductFiltersPage } >( {
|
|||
|
||||
test.describe( `${ blockData.name }`, () => {
|
||||
test.beforeEach( async ( { admin, requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//${ blockData.slug }`,
|
||||
postType: 'wp_template',
|
||||
|
|
|
@ -15,9 +15,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( {
|
|||
test.describe.skip( 'Product Filter: Rating Filter Block', () => {
|
||||
test.describe( 'frontend', () => {
|
||||
test.beforeEach( async ( { requestUtils, templateCompiler } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await templateCompiler.compile( {
|
||||
attributes: {
|
||||
attributeId: 1,
|
||||
|
|
|
@ -15,9 +15,7 @@ const test = base.extend< { templateCompiler: TemplateCompiler } >( {
|
|||
test.describe.skip( 'Product Filter: Stock Status Block', () => {
|
||||
test.describe( 'With default display style', () => {
|
||||
test.beforeEach( async ( { requestUtils, templateCompiler } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await templateCompiler.compile();
|
||||
} );
|
||||
|
||||
|
@ -109,9 +107,7 @@ test.describe.skip( 'Product Filter: Stock Status Block', () => {
|
|||
|
||||
test.describe( 'With dropdown display style', () => {
|
||||
test.beforeEach( async ( { requestUtils, templateCompiler } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-enable-experimental-features'
|
||||
);
|
||||
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
|
||||
await templateCompiler.compile( {
|
||||
attributes: {
|
||||
displayStyle: 'dropdown',
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { RequestUtils } from './index';
|
||||
|
||||
export async function setFeatureFlag(
|
||||
this: RequestUtils,
|
||||
flag: string,
|
||||
value: boolean
|
||||
) {
|
||||
return this.rest( {
|
||||
method: 'POST',
|
||||
path: '/e2e-feature-flags/update',
|
||||
data: { [ flag ]: value },
|
||||
failOnStatusCode: true,
|
||||
} );
|
||||
}
|
||||
|
||||
export async function resetFeatureFlag( this: RequestUtils ) {
|
||||
return this.rest( {
|
||||
method: 'GET',
|
||||
path: '/e2e-feature-flags/reset',
|
||||
failOnStatusCode: true,
|
||||
} );
|
||||
}
|
|
@ -13,6 +13,7 @@ import {
|
|||
createTemplateFromFile,
|
||||
TemplateCompiler,
|
||||
} from './templates';
|
||||
import { resetFeatureFlag, setFeatureFlag } from './feature-flag';
|
||||
|
||||
export class RequestUtils extends CoreRequestUtils {
|
||||
/** @borrows getTemplates as this.getTemplates */
|
||||
|
@ -25,6 +26,10 @@ export class RequestUtils extends CoreRequestUtils {
|
|||
/** @borrows createTemplateFromFile as this.createTemplateFromFile */
|
||||
createTemplateFromFile: typeof createTemplateFromFile =
|
||||
createTemplateFromFile.bind( this );
|
||||
/** @borrows setFeatureFlag as this.setFeatureFlag */
|
||||
setFeatureFlag: typeof setFeatureFlag = setFeatureFlag.bind( this );
|
||||
/** @borrows resetFeatureFlag as this.resetFeatureFlag */
|
||||
resetFeatureFlag: typeof resetFeatureFlag = resetFeatureFlag.bind( this );
|
||||
}
|
||||
|
||||
export { TemplateCompiler, PostCompiler };
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: Increases frequency of cron job from daily to twice daily.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Adds constants for all legacy order statuses to centralize them, reduce typos, improve code strictness, ease status lookups, and enhance documentation.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: enhancement
|
||||
|
||||
Removed CouponPageMoved class.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Prevented phone numbers containing Unicode control characters from failing validation during Checkout.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue