From fc1745b03b0fcaa46cb95aa9bbc74eb334b215a5 Mon Sep 17 00:00:00 2001 From: louwie17 Date: Thu, 19 Jan 2023 04:52:02 -0400 Subject: [PATCH] Add/36075 render fields sections from php config (#36414) * Add initial component to auto load fills from API config * Add changelog * Update logic to make use of new store and re-usable components * Add changelog * Add loading state for product form data to add/edit product pages --- ...075_render_fields_sections_from_php_config | 4 ++ packages/js/data/src/index.ts | 9 +++- packages/js/data/src/product-form/actions.ts | 4 +- .../js/data/src/product-form/resolvers.ts | 4 +- .../js/data/src/product-form/selectors.ts | 7 +++ packages/js/data/src/product-form/types.ts | 9 ++-- .../client/products/add-product-page.tsx | 26 +++++++++- .../client/products/edit-product-page.tsx | 7 ++- .../details-section/details-section-fills.tsx | 1 + .../client/products/fills/index.ts | 5 ++ .../fills/product-form-field-fills.tsx | 37 +++++++++++++ .../products/fills/product-form-fills.tsx | 52 +++++++++++++++++++ .../fills/product-form-section-fills.tsx | 34 ++++++++++++ .../client/products/product-form.tsx | 4 +- ...075_render_fields_sections_from_php_config | 4 ++ 15 files changed, 192 insertions(+), 15 deletions(-) create mode 100644 packages/js/data/changelog/add-36075_render_fields_sections_from_php_config create mode 100644 plugins/woocommerce-admin/client/products/fills/product-form-field-fills.tsx create mode 100644 plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx create mode 100644 plugins/woocommerce-admin/client/products/fills/product-form-section-fills.tsx create mode 100644 plugins/woocommerce/changelog/add-36075_render_fields_sections_from_php_config diff --git a/packages/js/data/changelog/add-36075_render_fields_sections_from_php_config b/packages/js/data/changelog/add-36075_render_fields_sections_from_php_config new file mode 100644 index 00000000000..9c3dfc710c8 --- /dev/null +++ b/packages/js/data/changelog/add-36075_render_fields_sections_from_php_config @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Tweak the product form types and exports. diff --git a/packages/js/data/src/index.ts b/packages/js/data/src/index.ts index c0af7842a32..8fd66a820d8 100644 --- a/packages/js/data/src/index.ts +++ b/packages/js/data/src/index.ts @@ -76,7 +76,11 @@ export { // Export types export * from './types'; export * from './countries/types'; -export { ProductForm } from './product-form/types'; +export { + ProductForm, + ProductFormField, + ProductFormSection, +} from './product-form/types'; export * from './onboarding/types'; export * from './plugins/types'; export * from './products/types'; @@ -172,6 +176,7 @@ import { ProductCategorySelectors } from './product-categories/types'; import { ProductAttributeTermsSelectors } from './product-attribute-terms/types'; import { ProductVariationSelectors } from './product-variations/types'; import { TaxClassSelectors } from './tax-classes/types'; +import { ProductFormSelectors } from './product-form/selectors'; // As we add types to all the package selectors we can fill out these unknown types with real ones. See one // of the already typed selectors for an example of how you can do this. @@ -219,6 +224,8 @@ export type WCSelectorType< T > = T extends typeof REVIEWS_STORE_NAME ? ShippingZonesSelectors : T extends typeof EXPERIMENTAL_TAX_CLASSES_STORE_NAME ? TaxClassSelectors + : T extends typeof EXPERIMENTAL_PRODUCT_FORM_STORE_NAME + ? ProductFormSelectors : never; export interface WCDataSelector { diff --git a/packages/js/data/src/product-form/actions.ts b/packages/js/data/src/product-form/actions.ts index e5e2288a838..bba4057dc1c 100644 --- a/packages/js/data/src/product-form/actions.ts +++ b/packages/js/data/src/product-form/actions.ts @@ -2,9 +2,9 @@ * Internal dependencies */ import TYPES from './action-types'; -import { Field, ProductForm } from './types'; +import { ProductFormField, ProductForm } from './types'; -export function getFieldsSuccess( fields: Field[] ) { +export function getFieldsSuccess( fields: ProductFormField[] ) { return { type: TYPES.GET_FIELDS_SUCCESS as const, fields, diff --git a/packages/js/data/src/product-form/resolvers.ts b/packages/js/data/src/product-form/resolvers.ts index 632a2dc5a57..8f9073cd192 100644 --- a/packages/js/data/src/product-form/resolvers.ts +++ b/packages/js/data/src/product-form/resolvers.ts @@ -14,7 +14,7 @@ import { getProductFormError, } from './actions'; import { WC_ADMIN_NAMESPACE } from '../constants'; -import { Field, ProductForm } from './types'; +import { ProductFormField, ProductForm } from './types'; import { STORE_NAME } from './constants'; const resolveSelect = @@ -23,7 +23,7 @@ const resolveSelect = export function* getFields() { try { const url = WC_ADMIN_NAMESPACE + '/product-form/fields'; - const results: Field[] = yield apiFetch( { + const results: ProductFormField[] = yield apiFetch( { path: url, method: 'GET', } ); diff --git a/packages/js/data/src/product-form/selectors.ts b/packages/js/data/src/product-form/selectors.ts index d986b006cc7..bb1f0501bb9 100644 --- a/packages/js/data/src/product-form/selectors.ts +++ b/packages/js/data/src/product-form/selectors.ts @@ -1,6 +1,7 @@ /** * Internal dependencies */ +import { WPDataSelector, WPDataSelectors } from '../types'; import { ProductFormState } from './types'; export const getFields = ( state: ProductFormState ) => { @@ -15,3 +16,9 @@ export const getProductForm = ( state: ProductFormState ) => { const { errors, ...form } = state; return form; }; + +export type ProductFormSelectors = { + getFields: WPDataSelector< typeof getFields >; + getField: WPDataSelector< typeof getField >; + getProductForm: WPDataSelector< typeof getProductForm >; +} & WPDataSelectors; diff --git a/packages/js/data/src/product-form/types.ts b/packages/js/data/src/product-form/types.ts index d4dcc8319b1..e9b86eadaea 100644 --- a/packages/js/data/src/product-form/types.ts +++ b/packages/js/data/src/product-form/types.ts @@ -9,22 +9,23 @@ type FieldProperties = { label: string; }; -export type Field = BaseComponent & { +export type ProductFormField = BaseComponent & { type: string; section: string; properties: FieldProperties; }; -export type Section = BaseComponent & { +export type ProductFormSection = BaseComponent & { title: string; description: string; + location: string; }; export type Subsection = BaseComponent; export type ProductForm = { - fields: Field[]; - sections: Section[]; + fields: ProductFormField[]; + sections: ProductFormSection[]; subsections: Subsection[]; }; diff --git a/plugins/woocommerce-admin/client/products/add-product-page.tsx b/plugins/woocommerce-admin/client/products/add-product-page.tsx index 7702b9e5ac8..34c0126b64f 100644 --- a/plugins/woocommerce-admin/client/products/add-product-page.tsx +++ b/plugins/woocommerce-admin/client/products/add-product-page.tsx @@ -2,7 +2,13 @@ * External dependencies */ import { recordEvent } from '@woocommerce/tracks'; +import { useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; +import { Spinner } from '@wordpress/components'; +import { + EXPERIMENTAL_PRODUCT_FORM_STORE_NAME, + WCDataSelector, +} from '@woocommerce/data'; /** * Internal dependencies @@ -10,16 +16,32 @@ import { useEffect } from '@wordpress/element'; import { ProductForm } from './product-form'; import { ProductTourContainer } from './tour'; import './product-page.scss'; +import './fills'; const AddProductPage: React.FC = () => { + const { isLoading } = useSelect( ( select: WCDataSelector ) => { + const { hasFinishedResolution: hasProductFormFinishedResolution } = + select( EXPERIMENTAL_PRODUCT_FORM_STORE_NAME ); + return { + isLoading: ! hasProductFormFinishedResolution( 'getProductForm' ), + }; + } ); useEffect( () => { recordEvent( 'view_new_product_management_experience' ); }, [] ); return (
- - + { isLoading ? ( +
+ +
+ ) : ( + <> + + + + ) }
); }; diff --git a/plugins/woocommerce-admin/client/products/edit-product-page.tsx b/plugins/woocommerce-admin/client/products/edit-product-page.tsx index 6606da226db..d7e2bffc8dd 100644 --- a/plugins/woocommerce-admin/client/products/edit-product-page.tsx +++ b/plugins/woocommerce-admin/client/products/edit-product-page.tsx @@ -3,6 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { + EXPERIMENTAL_PRODUCT_FORM_STORE_NAME, EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, PartialProduct, Product, @@ -21,6 +22,7 @@ import { ProductForm } from './product-form'; import { ProductFormLayout } from './layout/product-form-layout'; import { ProductVariationForm } from './product-variation-form'; import './product-page.scss'; +import './fills'; const EditProductPage: React.FC = () => { const { productId, variationId } = useParams(); @@ -35,6 +37,8 @@ const EditProductPage: React.FC = () => { isPending, getPermalinkParts, } = select( PRODUCTS_STORE_NAME ); + const { hasFinishedResolution: hasProductFormFinishedResolution } = + select( EXPERIMENTAL_PRODUCT_FORM_STORE_NAME ); const { getProductVariation, hasFinishedResolution: hasProductVariationFinishedResolution, @@ -71,7 +75,8 @@ const EditProductPage: React.FC = () => { 'getProductVariation', [ parseInt( variationId, 10 ) ] ) - ), + ) || + ! hasProductFormFinishedResolution( 'getProductForm' ), isPendingAction: isPending( 'createProduct' ) || isPending( diff --git a/plugins/woocommerce-admin/client/products/fills/details-section/details-section-fills.tsx b/plugins/woocommerce-admin/client/products/fills/details-section/details-section-fills.tsx index dae3909e352..73646b5d61c 100644 --- a/plugins/woocommerce-admin/client/products/fills/details-section/details-section-fills.tsx +++ b/plugins/woocommerce-admin/client/products/fills/details-section/details-section-fills.tsx @@ -28,6 +28,7 @@ const DetailsSection = () => ( id={ DETAILS_SECTION_ID } location="tab/general" pluginId="core" + order={ 1 } > = ( { + fields, +} ) => { + const { getInputProps } = useFormContext< Product >(); + + return ( + <> + { fields.map( ( field ) => ( + + <> + { renderField( field.type, { + ...getInputProps( field.properties.name ), + ...field.properties, + } ) } + + + ) ) }{ ' ' } + + ); +}; diff --git a/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx b/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx new file mode 100644 index 00000000000..0710edfd2fd --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import { registerPlugin } from '@wordpress/plugins'; +import { useSelect, resolveSelect } from '@wordpress/data'; +import { + EXPERIMENTAL_PRODUCT_FORM_STORE_NAME, + WCDataSelector, +} from '@woocommerce/data'; +import { registerCoreProductFields } from '@woocommerce/components'; + +registerCoreProductFields(); + +/** + * Internal dependencies + */ +import { Fields } from './product-form-field-fills'; +import { Sections } from './product-form-section-fills'; + +const Form = () => { + const { formData } = useSelect( ( select: WCDataSelector ) => { + return { + formData: select( + EXPERIMENTAL_PRODUCT_FORM_STORE_NAME + ).getProductForm(), + }; + } ); + + return ( + <> + { formData && ( + <> + + + + ) } + + ); +}; + +/** + * Preloading product form data, as product pages are waiting on this to be resolved. + * The above Form component won't get rendered until the getProductForm is resolved. + */ +resolveSelect( EXPERIMENTAL_PRODUCT_FORM_STORE_NAME ).getProductForm(); +registerPlugin( 'wc-admin-product-editor-form-fills', { + // @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. + scope: 'woocommerce-product-editor', + render: () => { + return
; + }, +} ); diff --git a/plugins/woocommerce-admin/client/products/fills/product-form-section-fills.tsx b/plugins/woocommerce-admin/client/products/fills/product-form-section-fills.tsx new file mode 100644 index 00000000000..87026629a88 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/product-form-section-fills.tsx @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Card, CardBody } from '@wordpress/components'; +import { + __experimentalWooProductSectionItem as WooProductSectionItem, + __experimentalProductFieldSection as ProductFieldSection, +} from '@woocommerce/components'; +import { ProductFormSection } from '@woocommerce/data'; + +export const Sections: React.FC< { sections: ProductFormSection[] } > = ( { + sections, +} ) => { + return ( + <> + { sections.map( ( section ) => ( + + + + ) ) } + + ); +}; diff --git a/plugins/woocommerce-admin/client/products/product-form.tsx b/plugins/woocommerce-admin/client/products/product-form.tsx index 655e10006d2..60abf3f26b5 100644 --- a/plugins/woocommerce-admin/client/products/product-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-form.tsx @@ -7,8 +7,8 @@ import { __experimentalWooProductSectionItem as WooProductSectionItem, } from '@woocommerce/components'; import { PartialProduct, Product } from '@woocommerce/data'; -import { Ref } from 'react'; import { PluginArea } from '@wordpress/plugins'; +import { Ref } from 'react'; /** * Internal dependencies @@ -26,8 +26,6 @@ import { OptionsSection } from './sections/options-section'; import { ProductFormFooter } from './layout/product-form-footer'; import { ProductFormTab } from './product-form-tab'; -import './fills'; - export const ProductForm: React.FC< { product?: PartialProduct; formRef?: Ref< FormRef< Partial< Product > > >; diff --git a/plugins/woocommerce/changelog/add-36075_render_fields_sections_from_php_config b/plugins/woocommerce/changelog/add-36075_render_fields_sections_from_php_config new file mode 100644 index 00000000000..4642e24d58f --- /dev/null +++ b/plugins/woocommerce/changelog/add-36075_render_fields_sections_from_php_config @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Minor adjustments to the ProductForm API