Product Block Editor: populate shipping class slug automatically (#47896)

* extend shipping_classses with '/suggest-slug' endpoint

* generate slug automatically

* changelog

* jsdoc

* polish setting and pulling the slug

* replace checkbox by button

* reword slug inout label with "Slug"

* add slug help

* use prefix input property

* isPrimary is deprecated. Use "variant" instead

* fixing typo. props to @mdperez86
This commit is contained in:
Damián Suárez 2024-05-30 12:05:34 +01:00 committed by GitHub
parent b0a1787304
commit ac6fe9f933
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 163 additions and 3 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
Product Block Editor: populate shipping class slug automatically

View File

@ -1,7 +1,6 @@
/**
* External dependencies
*/
import { Button, Modal, TextControl } from '@wordpress/components';
import {
useState,
createElement,
@ -10,6 +9,19 @@ import {
import { __ } from '@wordpress/i18n';
import { Form, FormErrors, useFormContext } from '@woocommerce/components';
import { ProductShippingClass } from '@woocommerce/data';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import {
Button,
Modal,
TextControl,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We need this to import the block modules for registration.
__experimentalInputControl as InputControl,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We need this to import the block modules for registration.
__experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
} from '@wordpress/components';
export type ShippingClassFormProps = {
onAdd: () => Promise< ProductShippingClass >;
@ -33,6 +45,58 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
} );
}
// State to control the automatic slug generation.
const [ isRequestingSlug, setIsRequestingSlug ] = useState( false );
// Get the shipping class name value.
const shippingNameInputValue = String( getInputProps( 'name' ).value );
const [ prevNameValue, setPrevNameValue ] = useState(
shippingNameInputValue
);
/**
* Pull the slug suggestion from the server,
* and update the slug input field.
*/
async function pullAndUpdateSlugInputField() {
setIsRequestingSlug( true );
// Avoid making the request if the name has not changed.
if ( prevNameValue === shippingNameInputValue ) {
return;
}
setPrevNameValue( shippingNameInputValue );
const url = `/wc/v3/products/shipping_classes/slug-suggestion`;
const slug: string = await apiFetch( {
path: addQueryArgs( url, { name: shippingNameInputValue } ),
method: 'GET',
} );
setIsRequestingSlug( false );
getInputProps( 'slug' ).onChange( slug );
}
const isGenerateButtonDisabled =
isRequestingSlug ||
! shippingNameInputValue?.length ||
prevNameValue === shippingNameInputValue;
/**
* Get a slug suggestion based on the shipping class name.
* This function is called when the name field is blurred.
*/
function getSlugSuggestion() {
if ( ! isRequestingSlug ) {
return;
}
pullAndUpdateSlugInputField();
}
return (
<div className="woocommerce-add-new-shipping-class-modal__wrapper">
<TextControl
@ -48,11 +112,36 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
),
}
) }
onBlur={ getSlugSuggestion }
/>
<TextControl
<InputControl
{ ...getInputProps( 'slug' ) }
label={ __( 'Slug', 'woocommerce' ) }
onChange={ ( value: string ) => {
setPrevNameValue( '' ); // clean the previous name value.
getInputProps( 'slug' ).onChange( value );
} }
disabled={ isRequestingSlug }
help={ __(
'Set a custom slug or generate it by clicking the button.',
'woocommerce'
) }
prefix={
<InputControlPrefixWrapper>
<Button
disabled={ isGenerateButtonDisabled }
variant="secondary"
onClick={ pullAndUpdateSlugInputField }
isBusy={ isRequestingSlug }
isSmall
>
{ __( 'Generate', 'woocommerce' ) }
</Button>
</InputControlPrefixWrapper>
}
/>
<TextControl
{ ...getInputProps( 'description' ) }
label={ __( 'Description', 'woocommerce' ) }
@ -64,12 +153,13 @@ function ShippingClassForm( { onAdd, onCancel }: ShippingClassFormProps ) {
)
}
/>
<div className="woocommerce-add-new-shipping-class-modal__buttons">
<Button isSecondary onClick={ onCancel }>
{ __( 'Cancel', 'woocommerce' ) }
</Button>
<Button
isPrimary
variant="primary"
isBusy={ isLoading }
disabled={ ! isValidForm || isLoading }
onClick={ handleAdd }

View File

@ -10,9 +10,14 @@
gap: 8px;
justify-content: flex-end;
}
.has-error {
.components-base-control__help {
color: $studio-red-50;
}
}
.components-input-control__container {
min-height: 36px;
}
}

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
REST API: extened shipping_classes namespace with the /suggest-slug endpoint

View File

@ -24,4 +24,61 @@ class WC_REST_Product_Shipping_Classes_Controller extends WC_REST_Product_Shippi
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Register the routes for product reviews.
*/
public function register_routes() {
parent::register_routes();
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/slug-suggestion',
array(
'args' => array(
'name' => array(
'description' => __( 'Suggest a slug for the term.', 'woocommerce' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'suggest_slug' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Callback fuction for the slug-suggestion endpoint.
*
* @param WP_REST_Request $request Full details about the request.
* @return string The suggested slug.
*/
public function suggest_slug( $request ) {
$name = $request['name'];
$slug = sanitize_title( $name ); // potential slug.
$term = get_term_by( 'slug', $slug, $this->taxonomy );
/*
* If the term exists, creates a unique slug
* based on the name provided.
* Otherwise, returns the sanitized name.
*/
if ( isset( $term->slug ) ) {
/*
* Pass a Term object that has only the taxonomy property,
* to induce the wp_unique_term_slug() function to generate a unique slug.
* Otherwise, the function will return the same slug.
* @see https://core.trac.wordpress.org/browser/tags/6.5/src/wp-includes/taxonomy.php#L3130
* @see https://github.com/WordPress/wordpress-develop/blob/a1b1e0339eb6dfa72a30933cac2a1c6ad2bbfe96/src/wp-includes/taxonomy.php#L3078-L3156
*/
$slug = wp_unique_term_slug( $slug, (object) array( 'taxonomy' => $this->taxonomy ) );
}
return rest_ensure_response( $slug );
}
}