341 lines
8.5 KiB
TypeScript
341 lines
8.5 KiB
TypeScript
/**
|
|
* External dependencies
|
|
*/
|
|
import { resolveSelect, useSelect } from '@wordpress/data';
|
|
import { useState, useEffect, useMemo } from '@wordpress/element';
|
|
import { store as coreStore } from '@wordpress/core-data';
|
|
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
|
|
type LocationType = 'product' | 'archive' | 'cart' | 'order' | 'generic';
|
|
type Context< T > = T & {
|
|
templateSlug?: string;
|
|
postId?: number;
|
|
};
|
|
type SetEntityId = (
|
|
kind: 'postType' | 'taxonomy',
|
|
name: 'product' | 'product_cat' | 'product_tag',
|
|
slug: string,
|
|
stateSetter: ( entityId: number | null ) => void
|
|
) => void;
|
|
|
|
const templateSlugs = {
|
|
singleProduct: 'single-product',
|
|
productCategory: 'taxonomy-product_cat',
|
|
productTag: 'taxonomy-product_tag',
|
|
productAttribute: 'taxonomy-product_attribute',
|
|
orderConfirmation: 'order-confirmation',
|
|
cart: 'page-cart',
|
|
checkout: 'page-checkout',
|
|
};
|
|
|
|
const getIdFromResponse = ( resp?: Record< 'id', number >[] ): number | null =>
|
|
resp && resp.length && resp[ 0 ]?.id ? resp[ 0 ].id : null;
|
|
|
|
const setEntityId: SetEntityId = async ( kind, name, slug, stateSetter ) => {
|
|
const response = ( await resolveSelect( coreStore ).getEntityRecords(
|
|
kind,
|
|
name,
|
|
{
|
|
_fields: [ 'id' ],
|
|
slug,
|
|
}
|
|
) ) as Record< 'id', number >[];
|
|
const entityId = getIdFromResponse( response );
|
|
stateSetter( entityId );
|
|
};
|
|
|
|
const prepareGetEntitySlug =
|
|
( templateSlug: string ) =>
|
|
( entitySlug: string ): string =>
|
|
templateSlug.replace( `${ entitySlug }-`, '' );
|
|
const prepareIsInSpecificTemplate =
|
|
( templateSlug: string ) =>
|
|
( entitySlug: string ): boolean =>
|
|
templateSlug.includes( entitySlug ) && templateSlug !== entitySlug;
|
|
const prepareIsInGenericTemplate =
|
|
( templateSlug: string ) =>
|
|
( entitySlug: string ): boolean =>
|
|
templateSlug === entitySlug;
|
|
|
|
const createLocationObject = ( type: LocationType, sourceData = {} ) => ( {
|
|
type,
|
|
sourceData,
|
|
} );
|
|
|
|
type ContextProperties = {
|
|
templateSlug: string;
|
|
postId?: string;
|
|
};
|
|
|
|
export const useGetLocation = < T, >(
|
|
context: Context< T & ContextProperties >,
|
|
clientId: string
|
|
) => {
|
|
const templateSlug = context.templateSlug || '';
|
|
const postId = context.postId || null;
|
|
|
|
const getEntitySlug = prepareGetEntitySlug( templateSlug );
|
|
const isInSpecificTemplate = prepareIsInSpecificTemplate( templateSlug );
|
|
|
|
// Detect Specific Templates
|
|
const isInSpecificProductTemplate = isInSpecificTemplate(
|
|
templateSlugs.singleProduct
|
|
);
|
|
const isInSpecificCategoryTemplate = isInSpecificTemplate(
|
|
templateSlugs.productCategory
|
|
);
|
|
const isInSpecificTagTemplate = isInSpecificTemplate(
|
|
templateSlugs.productTag
|
|
);
|
|
|
|
const [ productId, setProductId ] = useState< number | null >( null );
|
|
const [ categoryId, setCategoryId ] = useState< number | null >( null );
|
|
const [ tagId, setTagId ] = useState< number | null >( null );
|
|
|
|
useEffect( () => {
|
|
if ( isInSpecificProductTemplate ) {
|
|
const slug = getEntitySlug( templateSlugs.singleProduct );
|
|
setEntityId( 'postType', 'product', slug, setProductId );
|
|
}
|
|
|
|
if ( isInSpecificCategoryTemplate ) {
|
|
const slug = getEntitySlug( templateSlugs.productCategory );
|
|
setEntityId( 'taxonomy', 'product_cat', slug, setCategoryId );
|
|
}
|
|
|
|
if ( isInSpecificTagTemplate ) {
|
|
const slug = getEntitySlug( templateSlugs.productTag );
|
|
setEntityId( 'taxonomy', 'product_tag', slug, setTagId );
|
|
}
|
|
}, [
|
|
isInSpecificProductTemplate,
|
|
isInSpecificCategoryTemplate,
|
|
isInSpecificTagTemplate,
|
|
getEntitySlug,
|
|
] );
|
|
|
|
const { isInSingleProductBlock, isInMiniCartBlock } = useSelect(
|
|
( select ) => ( {
|
|
isInSingleProductBlock:
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore No types for this selector exist yet
|
|
select( blockEditorStore ).getBlockParentsByBlockName(
|
|
clientId,
|
|
'woocommerce/single-product'
|
|
).length > 0,
|
|
isInMiniCartBlock:
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore No types for this selector exist yet
|
|
select( blockEditorStore ).getBlockParentsByBlockName(
|
|
clientId,
|
|
'woocommerce/mini-cart-contents'
|
|
).length > 0,
|
|
} ),
|
|
[ clientId ]
|
|
);
|
|
|
|
/**
|
|
* Case 1.1: SPECIFIC PRODUCT
|
|
* Single Product block - take product ID from context
|
|
*/
|
|
|
|
if ( isInSingleProductBlock ) {
|
|
return createLocationObject( 'product', { productId: postId } );
|
|
}
|
|
|
|
/**
|
|
* Case 1.2: SPECIFIC PRODUCT
|
|
* Specific Single Product template - take product ID from taxononmy
|
|
*/
|
|
|
|
if ( isInSpecificProductTemplate ) {
|
|
return createLocationObject( 'product', { productId } );
|
|
}
|
|
|
|
const isInGenericTemplate = prepareIsInGenericTemplate( templateSlug );
|
|
|
|
/**
|
|
* Case 1.3: GENERIC PRODUCT
|
|
* Generic Single Product template
|
|
*/
|
|
|
|
const isInSingleProductTemplate = isInGenericTemplate(
|
|
templateSlugs.singleProduct
|
|
);
|
|
|
|
if ( isInSingleProductTemplate ) {
|
|
return createLocationObject( 'product', { productId: null } );
|
|
}
|
|
|
|
/**
|
|
* Case 2.1: SPECIFIC TAXONOMY
|
|
* Specific Category template - take category ID from
|
|
*/
|
|
|
|
if ( isInSpecificCategoryTemplate ) {
|
|
return createLocationObject( 'archive', {
|
|
taxonomy: 'product_cat',
|
|
termId: categoryId,
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Case 2.2: SPECIFIC TAXONOMY
|
|
* Specific Tag template
|
|
*/
|
|
|
|
if ( isInSpecificTagTemplate ) {
|
|
return createLocationObject( 'archive', {
|
|
taxonomy: 'product_tag',
|
|
termId: tagId,
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Case 2.3: GENERIC TAXONOMY
|
|
* Generic Taxonomy template
|
|
*/
|
|
|
|
const isInProductsByCategoryTemplate = isInGenericTemplate(
|
|
templateSlugs.productCategory
|
|
);
|
|
|
|
if ( isInProductsByCategoryTemplate ) {
|
|
return createLocationObject( 'archive', {
|
|
taxonomy: 'product_cat',
|
|
termId: null,
|
|
} );
|
|
}
|
|
|
|
const isInProductsByTagTemplate = isInGenericTemplate(
|
|
templateSlugs.productTag
|
|
);
|
|
|
|
if ( isInProductsByTagTemplate ) {
|
|
return createLocationObject( 'archive', {
|
|
taxonomy: 'product_tag',
|
|
termId: null,
|
|
} );
|
|
}
|
|
|
|
const isInProductsByAttributeTemplate = isInGenericTemplate(
|
|
templateSlugs.productAttribute
|
|
);
|
|
|
|
if ( isInProductsByAttributeTemplate ) {
|
|
return createLocationObject( 'archive', {
|
|
taxonomy: null,
|
|
termId: null,
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* Case 3: GENERIC CART
|
|
* Cart/Checkout templates or Mini Cart
|
|
*/
|
|
|
|
const isInCartContext =
|
|
templateSlug === templateSlugs.cart ||
|
|
templateSlug === templateSlugs.checkout ||
|
|
isInMiniCartBlock;
|
|
|
|
if ( isInCartContext ) {
|
|
return createLocationObject( 'cart' );
|
|
}
|
|
|
|
/**
|
|
* Case 4: GENERIC ORDER
|
|
* Order Confirmation template
|
|
*/
|
|
|
|
const isInOrderTemplate = isInGenericTemplate(
|
|
templateSlugs.orderConfirmation
|
|
);
|
|
|
|
if ( isInOrderTemplate ) {
|
|
return createLocationObject( 'order' );
|
|
}
|
|
|
|
/**
|
|
* Case 5: GENERIC
|
|
* All other cases
|
|
*/
|
|
|
|
return createLocationObject( 'generic' );
|
|
};
|
|
|
|
/**
|
|
* In Product Collection block, queryContextIncludes attribute contains
|
|
* list of attribute names that should be included in the query context.
|
|
*
|
|
* This hook returns the query context object based on the attribute names
|
|
* provided in the queryContextIncludes array.
|
|
*
|
|
* Example:
|
|
* {
|
|
* clientID = 'd2c7e34f-70d6-417c-b582-f554a3a575f3',
|
|
* queryContextIncludes = [ 'collection' ]
|
|
* }
|
|
*
|
|
* The hook will return the following query context object:
|
|
* {
|
|
* collection: 'woocommerce/product-collection/featured'
|
|
* }
|
|
*
|
|
* @param args Arguments for the hook.
|
|
* @param args.clientId Client ID of the inner block.
|
|
* @param args.queryContextIncludes Array of attribute names to be included in the query context.
|
|
*
|
|
* @return Query context object.
|
|
*/
|
|
export const useProductCollectionQueryContext = ( {
|
|
clientId,
|
|
queryContextIncludes,
|
|
}: {
|
|
clientId: string;
|
|
queryContextIncludes: string[];
|
|
} ) => {
|
|
const productCollectionBlockAttributes = useSelect(
|
|
( select ) => {
|
|
const { getBlockParentsByBlockName, getBlockAttributes } =
|
|
select( 'core/block-editor' );
|
|
|
|
const parentBlocksClientIds = getBlockParentsByBlockName(
|
|
clientId,
|
|
'woocommerce/product-collection',
|
|
true
|
|
);
|
|
|
|
if ( parentBlocksClientIds?.length ) {
|
|
const closestParentClientId = parentBlocksClientIds[ 0 ];
|
|
return getBlockAttributes( closestParentClientId );
|
|
}
|
|
|
|
return null;
|
|
},
|
|
[ clientId ]
|
|
);
|
|
|
|
return useMemo( () => {
|
|
// If the product collection block is not found, return null.
|
|
if ( ! productCollectionBlockAttributes ) {
|
|
return null;
|
|
}
|
|
|
|
const queryContext: {
|
|
[ key: string ]: unknown;
|
|
} = {};
|
|
|
|
if ( queryContextIncludes?.length ) {
|
|
queryContextIncludes.forEach( ( attribute: string ) => {
|
|
if ( productCollectionBlockAttributes?.[ attribute ] ) {
|
|
queryContext[ attribute ] =
|
|
productCollectionBlockAttributes[ attribute ];
|
|
}
|
|
} );
|
|
}
|
|
|
|
return queryContext;
|
|
}, [ queryContextIncludes, productCollectionBlockAttributes ] );
|
|
};
|