Fix templates not accessible when bought via an unlisted bundle (#7844)

This commit is contained in:
Clément Pasteau
2025-09-23 11:18:15 +02:00
committed by GitHub
parent b7f7a39aa7
commit d6d7c5c1fb

View File

@@ -113,7 +113,32 @@ export const getOtherProductsFromSameAuthorTiles = <
.filter(Boolean);
};
export const getBundlesContainingProduct = <
export const getReceivedBundlesContainingProduct = <
U: PrivateAssetPack | PrivateGameTemplate | Bundle | Course
>({
product,
receivedProducts,
}: {|
product: U,
receivedProducts: ?Array<U>,
|}): Array<U> => {
if (!receivedProducts) return [];
return receivedProducts.filter(receivedProduct => {
return (
(receivedProduct.includedTemplateIds &&
receivedProduct.includedTemplateIds.includes(product.id)) ||
(receivedProduct.includedPackIds &&
receivedProduct.includedPackIds.includes(product.id)) ||
(receivedProduct.includedProducts &&
receivedProduct.includedProducts.some(
includedProduct => includedProduct.productId === product.id
))
);
});
};
export const getProductListingDatasContainingProduct = <
T:
| PrivateAssetPackListingData
| PrivateGameTemplateListingData
@@ -184,42 +209,49 @@ export const getBundlesContainingProductTiles = <
|}): ?Array<React.Node> => {
if (!product || !productListingDatas) return null;
const bundlesContainingProduct = getBundlesContainingProduct({
product,
productListingDatas,
});
const productListingDatasContainingProduct = getProductListingDatasContainingProduct(
{
product,
productListingDatas,
}
);
if (!bundlesContainingProduct.length) return null;
if (!productListingDatasContainingProduct.length) return null;
const ownedBundlesContainingProduct = bundlesContainingProduct.filter(
bundleContainingProduct =>
const ownedBundleListingDatasContainingProduct = productListingDatasContainingProduct.filter(
bundleListingDataContainingProduct =>
!!receivedProducts &&
!!receivedProducts.find(
Product => Product.id === bundleContainingProduct.id
Product => Product.id === bundleListingDataContainingProduct.id
)
);
const notOwnedBundlesContainingProduct = bundlesContainingProduct.filter(
bundleContainingProduct =>
!ownedBundlesContainingProduct.find(
ownedBundleContainingProduct =>
ownedBundleContainingProduct.id === bundleContainingProduct.id
const notOwnedBundleListingDatasContainingProduct = productListingDatasContainingProduct.filter(
bundleListingDataContainingProduct =>
!ownedBundleListingDatasContainingProduct.find(
ownedBundleListingDataContainingProduct =>
ownedBundleListingDataContainingProduct.id ===
bundleListingDataContainingProduct.id
)
);
const allProductsWithOwnedStatus = [
...ownedBundlesContainingProduct.map(bundleContainingProduct => ({
product: bundleContainingProduct,
owned: true,
})),
...notOwnedBundlesContainingProduct.map(bundleContainingProduct => ({
product: bundleContainingProduct,
owned: false,
})),
const allProductListingDatasWithOwnedStatus = [
...ownedBundleListingDatasContainingProduct.map(
bundleListingDataContainingProduct => ({
product: bundleListingDataContainingProduct,
owned: true,
})
),
...notOwnedBundleListingDatasContainingProduct.map(
bundleListingDataContainingProduct => ({
product: bundleListingDataContainingProduct,
owned: false,
})
),
];
return allProductsWithOwnedStatus.map(
({ product: bundleContainingProduct, owned }) => {
if (bundleContainingProduct.productType === 'ASSET_PACK') {
return allProductListingDatasWithOwnedStatus.map(
({ product: bundleListingDataContainingProduct, owned }) => {
if (bundleListingDataContainingProduct.productType === 'ASSET_PACK') {
if (!onPrivateAssetPackOpen) {
console.error(
'Trying to render a promo ASSET_PACK tile without onPrivateAssetPackOpen handler.'
@@ -228,16 +260,18 @@ export const getBundlesContainingProductTiles = <
}
return (
<PromoBundleCard
bundleProductListingData={bundleContainingProduct}
bundleProductListingData={bundleListingDataContainingProduct}
includedProductListingData={productListingData}
onSelect={() => onPrivateAssetPackOpen(bundleContainingProduct)}
onSelect={() =>
onPrivateAssetPackOpen(bundleListingDataContainingProduct)
}
owned={owned}
key={bundleContainingProduct.id}
key={bundleListingDataContainingProduct.id}
/>
);
}
if (bundleContainingProduct.productType === 'GAME_TEMPLATE') {
if (bundleListingDataContainingProduct.productType === 'GAME_TEMPLATE') {
if (!onPrivateGameTemplateOpen) {
console.error(
'Trying to render a promo GAME_TEMPLATE tile without onPrivateGameTemplateOpen handler.'
@@ -246,16 +280,18 @@ export const getBundlesContainingProductTiles = <
}
return (
<PromoBundleCard
bundleProductListingData={bundleContainingProduct}
bundleProductListingData={bundleListingDataContainingProduct}
includedProductListingData={productListingData}
onSelect={() => onPrivateGameTemplateOpen(bundleContainingProduct)}
onSelect={() =>
onPrivateGameTemplateOpen(bundleListingDataContainingProduct)
}
owned={owned}
key={bundleContainingProduct.id}
key={bundleListingDataContainingProduct.id}
/>
);
}
if (bundleContainingProduct.productType === 'BUNDLE') {
if (bundleListingDataContainingProduct.productType === 'BUNDLE') {
if (!onBundleOpen) {
console.error(
'Trying to render a promo BUNDLE tile without onBundleOpen handler.'
@@ -264,18 +300,18 @@ export const getBundlesContainingProductTiles = <
}
return (
<PromoBundleCard
bundleProductListingData={bundleContainingProduct}
bundleProductListingData={bundleListingDataContainingProduct}
includedProductListingData={productListingData}
onSelect={() => onBundleOpen(bundleContainingProduct)}
onSelect={() => onBundleOpen(bundleListingDataContainingProduct)}
owned={owned}
key={bundleContainingProduct.id}
key={bundleListingDataContainingProduct.id}
/>
);
}
console.error(
'Unexpected product type for Promo Tile:',
bundleContainingProduct.productType
bundleListingDataContainingProduct.productType
);
return null;
}
@@ -470,6 +506,7 @@ export const getUserProductPurchaseUsageType = <
productPurchases: ?Array<Purchase>,
allProductListingDatas: ?Array<T>,
|}): ?string => {
if (!productId) return null;
// User is not authenticated or still loading.
if (!receivedProducts || !productPurchases || !allProductListingDatas)
return null;
@@ -485,31 +522,35 @@ export const getUserProductPurchaseUsageType = <
);
if (!productPurchase) {
// It is possible the user has the product as part of a bundle.
const bundlesIncludingProduct = getBundlesContainingProduct({
product: currentReceivedProduct,
productListingDatas: allProductListingDatas,
});
if (!bundlesIncludingProduct.length) return null;
// It's important to look at the receivedProducts and not the productListingDatas,
// as the user might have received a product that is not listed anymore.
const receivedBundlesIncludingProduct = getReceivedBundlesContainingProduct(
{
product: currentReceivedProduct,
receivedProducts,
}
);
if (!receivedBundlesIncludingProduct.length) return null;
// We look at all the purchases of the bundles that include the product.
const receivedProductBundlePurchases = productPurchases.filter(
productPurchase =>
bundlesIncludingProduct.some(
receivedBundlesIncludingProduct.some(
bundleListingData =>
bundleListingData.id === productPurchase.productId
)
);
// No purchase found for the bundles including the product.
if (!receivedProductBundlePurchases.length) {
return null;
}
// We don't really know which usage type to return, so we look at the first purchase.
if (bundlesIncludingProduct[0].productType === 'BUNDLE') {
if (receivedBundlesIncludingProduct[0].includedProducts) {
// In a bundle, we look for the usage type of the included product.
const includedProduct = (
bundlesIncludingProduct[0].includedListableProducts || []
).find(includedProduct => includedProduct.productId === productId);
const includedProduct = receivedBundlesIncludingProduct[0].includedProducts.find(
includedProduct => includedProduct.productId === productId
);
return includedProduct ? includedProduct.usageType : null;
}