diff --git a/assets/js/atomic/blocks/product-elements/price/attributes-new.ts b/assets/js/atomic/blocks/product-elements/price/attributes-new.ts new file mode 100644 index 00000000000..db985052251 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/attributes-new.ts @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { BlockAttributes } from '@wordpress/blocks'; + +export const blockAttributes: BlockAttributes = { + isDescendentOfSingleProductTemplate: { + type: 'boolean', + default: false, + }, + isDescendentOfSingleProductBlock: { + type: 'boolean', + default: false, + }, + productId: { + type: 'number', + default: 0, + }, +}; + +export default blockAttributes; diff --git a/assets/js/atomic/blocks/product-elements/price/block-new-edit.tsx b/assets/js/atomic/blocks/product-elements/price/block-new-edit.tsx new file mode 100644 index 00000000000..f80d7d78b07 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/block-new-edit.tsx @@ -0,0 +1,146 @@ +/** + * External dependencies + */ +import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; +import { useEffect } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { + InnerBlockLayoutContextProvider, + useInnerBlockLayoutContext, + ProductDataContextProvider, +} from '@woocommerce/shared-context'; +import { useStoreProducts } from '@woocommerce/base-context/hooks'; + +/** + * Internal dependencies + */ +import { originalPriceName } from './inner-blocks'; +import { TEMPLATE } from './template'; + +interface Attributes { + isDescendentOfSingleProductBlock: boolean; + isDescendentOfSingleProductTemplate: boolean; + productId?: number; +} + +interface Context { + postId?: number; + queryId?: number; +} + +interface Props { + context: Context; + attributes: Attributes; + setAttributes: ( attributes: Partial< Attributes > ) => void; +} + +interface ContextProviderProps extends Props { + children: JSX.Element | JSX.Element[] | undefined; +} + +type ProductIdProps = Partial< ContextProviderProps > & { productId: number }; + +const ProviderFromAPI = ( { + productId, + children, +}: ProductIdProps ): JSX.Element => { + // TODO: this would be good to derive from the WP entity store at some point. + const { products, productsLoading } = useStoreProducts( { + include: productId, + } ); + let product = null; + if ( products.length > 0 ) { + product = + products.find( + ( productIteration ) => productIteration.id === productId + ) || null; + } + + return ( + + { children } + + ); +}; + +const DerivedProductDataContextProvider = ( { + context, + attributes, + setAttributes, + children, +}: ContextProviderProps ): JSX.Element => { + const { queryId, postId } = context; + const { productId } = attributes; + const isDescendentOfQueryLoop = Number.isFinite( queryId ); + const id = isDescendentOfQueryLoop ? postId : productId; + const isDescendentOfSingleProductTemplate = useSelect( + ( select ) => { + const editSiteStore = select( 'core/edit-site' ); + const editorPostId = editSiteStore?.getEditedPostId< + string | undefined + >(); + + return Boolean( + editorPostId?.includes( '//single-product' ) && + ! isDescendentOfQueryLoop + ); + }, + [ isDescendentOfQueryLoop ] + ); + + useEffect( + () => + setAttributes( { + isDescendentOfSingleProductTemplate, + } ), + [ isDescendentOfSingleProductTemplate, setAttributes ] + ); + if ( id && id > 0 ) { + return { children }; + } + return ( + + { children } + + ); +}; + +const EditBlock = ( { + context, + attributes, + setAttributes, +}: Props ): JSX.Element => { + const blockProps = useBlockProps(); + const { parentClassName } = useInnerBlockLayoutContext(); + return ( + +
+ +
+
+ ); +}; + +const Edit = ( props: Props ): JSX.Element => { + return ( + + + + ); +}; + +export default Edit; diff --git a/assets/js/atomic/blocks/product-elements/price/components/product-price.tsx b/assets/js/atomic/blocks/product-elements/price/components/product-price.tsx new file mode 100644 index 00000000000..06169c0eaae --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/components/product-price.tsx @@ -0,0 +1,58 @@ +/** + * External dependencies + */ +import type { Currency } from '@woocommerce/types'; +import type { CSSProperties } from 'react'; + +export interface ProductPriceProps { + /** + * CSS class for the wrapper + */ + wrapperClassName?: string | undefined; + /** + * Currency configuration object + */ + currency?: Currency; + /** + * The string version of the element to use for the price interpolation + * + * **Note:** It should contain `` (which is also the default value) + */ + format?: string; + /** + * The maximum price in a range + */ + maxPrice?: string | number; + /** + * The minimum price in a range + */ + minPrice?: string | number; + /** + * The current price + */ + price?: string | number; + /** + * CSS class for the price + */ + priceClassName?: string; + /** + * Custom style for the price + */ + priceStyle?: CSSProperties; + /** + * Custom style for the wrapper + */ + style?: CSSProperties; +} + +export const ProductPrice = ( { + className, + currency, + format = '', + maxPrice, + minPrice, + price, + priceClassName, + priceStyle, + style, +} ) => {}; diff --git a/assets/js/atomic/blocks/product-elements/price/index.tsx b/assets/js/atomic/blocks/product-elements/price/index-old.tsx similarity index 100% rename from assets/js/atomic/blocks/product-elements/price/index.tsx rename to assets/js/atomic/blocks/product-elements/price/index-old.tsx diff --git a/assets/js/atomic/blocks/product-elements/price/index.ts b/assets/js/atomic/blocks/product-elements/price/index.ts new file mode 100644 index 00000000000..5f1dc91e982 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/index.ts @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import sharedConfig from '../shared/config'; +import edit from './block-new-edit'; +import { save } from './save'; +import attributes from './attributes-new'; +import { supports } from './supports'; +import { + BLOCK_TITLE as title, + BLOCK_ICON as icon, + BLOCK_DESCRIPTION as description, +} from './constants'; + +const { ancestor, ...configuration } = sharedConfig; + +const blockConfig = { + ...configuration, + apiVersion: 2, + title, + description, + usesContext: [ 'postId', 'queryId' ], + providesContext: { + 'woocommerce/isDescendentOfSingleProductTemplate': + 'isDescendentOfSingleProductTemplate', + 'woocommerce/isDescendentOfSingleProductBlock': + 'isDescendentOfSingleProductBlock', + }, + icon: { src: icon }, + attributes, + supports, + edit, + save, +}; + +console.log( 'IN HERE - REGISTERING' ); + +registerBlockType( 'woocommerce/product-price', blockConfig ); diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/current-price/edit.tsx b/assets/js/atomic/blocks/product-elements/price/inner-blocks/current-price/edit.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/current-price/index.ts b/assets/js/atomic/blocks/product-elements/price/inner-blocks/current-price/index.ts new file mode 100644 index 00000000000..e5a36115057 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/inner-blocks/current-price/index.ts @@ -0,0 +1 @@ +export { Edit } from './edit'; diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/discount/edit.tsx b/assets/js/atomic/blocks/product-elements/price/inner-blocks/discount/edit.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/index.ts b/assets/js/atomic/blocks/product-elements/price/inner-blocks/index.ts new file mode 100644 index 00000000000..35b8672aad1 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/inner-blocks/index.ts @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import './original-price/index.ts'; + +export { BLOCK_NAME as originalPriceName } from './original-price/index'; diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/block.json b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/block.json new file mode 100644 index 00000000000..97db17d5bd2 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/block.json @@ -0,0 +1,12 @@ +{ + "name": "woocommerce/original-price", + "version": "1.0.0", + "icon": "info", + "title": "Original Price", + "description": "Display the original price for the product", + "category": "woocommerce", + "keywords": [ "WooCommerce" ], + "textdomain": "woo-gutenberg-products-block", + "apiVersion": 2, + "$schema": "https://schemas.wp.org/trunk/block.json" +} diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/block.tsx b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/block.tsx new file mode 100644 index 00000000000..fae25e4a7a5 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/block.tsx @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import type { HTMLAttributes } from 'react'; +import classnames from 'classnames'; +import { useStyleProps } from '@woocommerce/base-hooks'; +import { useInnerBlockLayoutContext } from '@woocommerce/shared-context'; +import ProductPrice from '@woocommerce/base-components/product-price'; + +/** + * Internal dependencies + */ +import type { PriceProps } from '../../types'; +//import './style.scss'; + +type Props = PriceProps & HTMLAttributes< HTMLDivElement >; + +const Block = ( { + attributes, + context, + rawPrice, + currency, +}: Props ): JSX.Element | null => { + const { className } = attributes; + const { isDescendentOfSingleProductTemplate = false } = context || {}; + const { className: stylesClassName, style } = useStyleProps( attributes ); + const { parentClassName } = useInnerBlockLayoutContext(); + const wrapperClassName = classnames( + 'wc-block-product-price__original', + className, + { + [ `${ parentClassName }__product-price` ]: parentClassName, + }, + stylesClassName + ); + if ( ! rawPrice && ! isDescendentOfSingleProductTemplate ) { + return ; + } + + const pricePreview = '5000'; + const priceClassName = classnames( { + [ `${ parentClassName }__product-original-price__value` ]: + parentClassName, + } ); + + return ( + + ); +}; + +export default Block; diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/edit.tsx b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/edit.tsx new file mode 100644 index 00000000000..61562df4eb1 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/edit.tsx @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { useBlockProps } from '@wordpress/block-editor'; +import type { HTMLAttributes, CSSProperties } from 'react'; +import { useProductDataContext } from '@woocommerce/shared-context'; +import { getCurrencyFromPriceResponse } from '@woocommerce/price-format'; + +/** + * Internal dependencies + */ +import PriceBlock from './block'; + +interface Attributes { + style: CSSProperties; +} + +type Props = { + attributes: Attributes; + context?: { isDescendentOfSingleProductTemplate: boolean }; +} & HTMLAttributes< HTMLDivElement >; + +const OriginalPriceEdit = ( { attributes, context }: Props ): JSX.Element => { + const blockProps = useBlockProps(); + const { product } = useProductDataContext(); + const { isDescendentOfSingleProductTemplate = false } = context || {}; + const originalPrice = product?.prices?.regular_price; + const currentPrice = product?.prices?.price; + const showPrice = originalPrice && currentPrice !== originalPrice; + const currency = isDescendentOfSingleProductTemplate + ? getCurrencyFromPriceResponse() + : getCurrencyFromPriceResponse( product?.prices ); + const blockAttrs = { + attributes, + currency, + context, + rawPrice: originalPrice, + }; + return ( + <> + { showPrice && ( +
+ +
+ ) } + + ); +}; + +export default OriginalPriceEdit; diff --git a/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/index.ts b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/index.ts new file mode 100644 index 00000000000..5cbea61364e --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/inner-blocks/original-price/index.ts @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import edit from './edit'; +import sharedConfig from '../../../shared/config'; +import metadata from './block.json'; + +const { ancestor, ...configuration } = sharedConfig; + +const blockConfig = { + ...configuration, + ...metadata, + edit, + supports: { + ...configuration.supports, + context: '', + }, +}; + +registerBlockType( metadata.name, blockConfig ); + +export const BLOCK_NAME = metadata.name; diff --git a/assets/js/atomic/blocks/product-elements/price/save.tsx b/assets/js/atomic/blocks/product-elements/price/save.tsx new file mode 100644 index 00000000000..5494846b102 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/save.tsx @@ -0,0 +1,17 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +export const save = ( { attributes } ) => { + if ( + attributes.isDescendentOfSingleProductBlock || + attributes.isDescendentOfSingleProductTemplate + ) { + return null; + } + + return ( +
+ ); +}; diff --git a/assets/js/atomic/blocks/product-elements/price/template.ts b/assets/js/atomic/blocks/product-elements/price/template.ts new file mode 100644 index 00000000000..0e11790a993 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/price/template.ts @@ -0,0 +1,8 @@ +/** + * External dependencies + */ +import { InnerBlockTemplate } from '@wordpress/blocks'; + +export const TEMPLATE: InnerBlockTemplate[] = [ + [ 'woocommerce/original-price', {}, [] ], +]; diff --git a/assets/js/atomic/blocks/product-elements/price/types.ts b/assets/js/atomic/blocks/product-elements/price/types.ts index bfbdb99815a..f66f5be2982 100644 --- a/assets/js/atomic/blocks/product-elements/price/types.ts +++ b/assets/js/atomic/blocks/product-elements/price/types.ts @@ -1,3 +1,10 @@ +/** + * External dependencies + */ +import type { CSSProperties } from 'react'; +import type { ProductPriceProps } from '@woocommerce/base-components/product-price'; + +// old code export interface BlockAttributes { productId?: number; className?: string; @@ -5,3 +12,18 @@ export interface BlockAttributes { isDescendentOfQueryLoop?: boolean; isDescendentOfSingleProductTemplate?: boolean; } + +export interface PriceContext { + isDescendentOfSingleProductTemplate: boolean; +} + +export interface PriceAttributes { + className?: string; + style: CSSProperties; +} + +export interface PriceProps extends ProductPriceProps { + attributes: PriceAttributes; + context?: PriceContext; + rawPrice?: string; +} diff --git a/assets/js/shared/context/product-data-context.tsx b/assets/js/shared/context/product-data-context.tsx index 7f6979060fe..500ea9c6ed4 100644 --- a/assets/js/shared/context/product-data-context.tsx +++ b/assets/js/shared/context/product-data-context.tsx @@ -69,7 +69,7 @@ const ProductDataContext = createContext( { export const useProductDataContext = () => useContext( ProductDataContext ); interface ProductDataContextProviderProps { - product: ProductResponseItem | null; + product?: ProductResponseItem | null; children: JSX.Element | JSX.Element[] | undefined; isLoading: boolean; }