mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-23 06:40:50 +01:00
Add catalog bundles
This commit is contained in:
parent
d783b9b2d1
commit
5c73d8d459
@ -65,7 +65,12 @@ export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, ac
|
|||||||
}
|
}
|
||||||
case CatalogActions.SET_CATALOG_PAGE_PARSER: {
|
case CatalogActions.SET_CATALOG_PAGE_PARSER: {
|
||||||
const pageParser = action.payload.pageParser;
|
const pageParser = action.payload.pageParser;
|
||||||
const activeOffer = null;
|
let activeOffer = null;
|
||||||
|
|
||||||
|
if(pageParser.layoutCode === 'single_bundle')
|
||||||
|
{
|
||||||
|
activeOffer = ((pageParser.offers && pageParser.offers[0]) || null);
|
||||||
|
}
|
||||||
|
|
||||||
const searchResult = state.searchResult;
|
const searchResult = state.searchResult;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FC, useEffect } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { GetCatalogPageComposer } from '../../../../../api/catalog/GetCatalogPageComposer';
|
import { GetCatalogPageComposer } from '../../../../../api/catalog/GetCatalogPageComposer';
|
||||||
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
|
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
|
||||||
import { CatalogIconView } from '../../../../catalog-icon/CatalogIconView';
|
import { CatalogIconView } from '../../../../catalog-icon/CatalogIconView';
|
||||||
@ -9,11 +9,14 @@ import { CatalogNavigationItemViewProps } from './CatalogNavigationItemView.type
|
|||||||
export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = props =>
|
export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { page = null, isActive = false, setActiveChild = null } = props;
|
const { page = null, isActive = false, setActiveChild = null } = props;
|
||||||
|
const [ isExpanded, setIsExpanded ] = useState(false);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!isActive || !page) return;
|
if(!isActive || !page) return;
|
||||||
|
|
||||||
|
setIsExpanded(true);
|
||||||
|
|
||||||
SendMessageHook(GetCatalogPageComposer(page.pageId, -1, CatalogMode.MODE_NORMAL));
|
SendMessageHook(GetCatalogPageComposer(page.pageId, -1, CatalogMode.MODE_NORMAL));
|
||||||
}, [ isActive, page ]);
|
}, [ isActive, page ]);
|
||||||
|
|
||||||
@ -21,12 +24,15 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
|||||||
{
|
{
|
||||||
if(!page) return;
|
if(!page) return;
|
||||||
|
|
||||||
setActiveChild(prevValue =>
|
setActiveChild(page);
|
||||||
{
|
|
||||||
if(prevValue === page) return null;
|
if(page.children && page.children.length)
|
||||||
|
{
|
||||||
return page;
|
setIsExpanded(prevValue =>
|
||||||
});
|
{
|
||||||
|
return !prevValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -34,9 +40,9 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
|||||||
<div className={ 'd-flex align-items-center cursor-pointer catalog-navigation-item ' + (isActive ? 'active ': '') } onClick={ select }>
|
<div className={ 'd-flex align-items-center cursor-pointer catalog-navigation-item ' + (isActive ? 'active ': '') } onClick={ select }>
|
||||||
<CatalogIconView icon={ page.icon } />
|
<CatalogIconView icon={ page.icon } />
|
||||||
<div className="flex-grow-1 text-black text-truncate px-1">{ page.localization }</div>
|
<div className="flex-grow-1 text-black text-truncate px-1">{ page.localization }</div>
|
||||||
{ (page.children.length > 0) && <i className={ 'fas fa-caret-' + (isActive ? 'up' : 'down') } /> }
|
{ (page.children.length > 0) && <i className={ 'fas fa-caret-' + (isExpanded ? 'up' : 'down') } /> }
|
||||||
</div>
|
</div>
|
||||||
{ isActive && page.children && (page.children.length > 0) &&
|
{ isActive && isExpanded && page.children && (page.children.length > 0) &&
|
||||||
<div className="d-flex flex-column mt-1">
|
<div className="d-flex flex-column mt-1">
|
||||||
<CatalogNavigationSetView page={ page } />
|
<CatalogNavigationSetView page={ page } />
|
||||||
</div> }
|
</div> }
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
@import './layout/CatalogLayout';
|
@import './layout/CatalogLayout';
|
||||||
@import './offer/CatalogPageOfferView';
|
@import './offer/CatalogPageOfferView';
|
||||||
@import './offers/CatalogPageOffersView';
|
@import './offers/CatalogPageOffersView';
|
||||||
|
@import './product/CatalogProductView';
|
||||||
@import './purchase/CatalogPurchaseView';
|
@import './purchase/CatalogPurchaseView';
|
||||||
@import './search-result/CatalogLayoutSearchResultView';
|
@import './search-result/CatalogLayoutSearchResultView';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
@import './default/CatalogLayoutDefaultView';
|
@import './default/CatalogLayoutDefaultView';
|
||||||
|
@import './single-bundle/CatalogLayoutSingleBundleView';
|
||||||
@import './spaces-new/CatalogLayoutSpacesView';
|
@import './spaces-new/CatalogLayoutSpacesView';
|
||||||
@import './trophies/CatalogLayoutTrophiesView';
|
@import './trophies/CatalogLayoutTrophiesView';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ICatalogPageParser, RoomPreviewer } from 'nitro-renderer';
|
import { ICatalogPageParser, RoomPreviewer } from 'nitro-renderer';
|
||||||
import { CatalogLayoutDefaultView } from './default/CatalogLayoutDefaultView';
|
import { CatalogLayoutDefaultView } from './default/CatalogLayoutDefaultView';
|
||||||
|
import { CatalogLayoutSingleBundleView } from './single-bundle/CatalogLayoutSingleBundleView';
|
||||||
import { CatalogLayoutSpacesView } from './spaces-new/CatalogLayoutSpacesView';
|
import { CatalogLayoutSpacesView } from './spaces-new/CatalogLayoutSpacesView';
|
||||||
import { CatalogLayoutTrophiesView } from './trophies/CatalogLayoutTrophiesView';
|
import { CatalogLayoutTrophiesView } from './trophies/CatalogLayoutTrophiesView';
|
||||||
|
|
||||||
@ -31,6 +32,8 @@ export function GetCatalogLayout(pageParser: ICatalogPageParser, roomPreviewer:
|
|||||||
return null;
|
return null;
|
||||||
case 'marketplace':
|
case 'marketplace':
|
||||||
return null;
|
return null;
|
||||||
|
case 'single_bundle':
|
||||||
|
return <CatalogLayoutSingleBundleView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
|
||||||
case 'spaces_new':
|
case 'spaces_new':
|
||||||
return <CatalogLayoutSpacesView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
|
return <CatalogLayoutSpacesView roomPreviewer={ roomPreviewer } pageParser={ pageParser } />;
|
||||||
case 'trophies':
|
case 'trophies':
|
||||||
|
@ -10,7 +10,7 @@ import { CatalogLayoutDefaultViewProps } from './CatalogLayoutDefaultView.types'
|
|||||||
export const CatalogLayoutDefaultView: FC<CatalogLayoutDefaultViewProps> = props =>
|
export const CatalogLayoutDefaultView: FC<CatalogLayoutDefaultViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { roomPreviewer = null, pageParser = null } = props;
|
const { roomPreviewer = null, pageParser = null } = props;
|
||||||
const { catalogState } = useCatalogContext();
|
const { catalogState = null } = useCatalogContext();
|
||||||
const { activeOffer = null } = catalogState;
|
const { activeOffer = null } = catalogState;
|
||||||
|
|
||||||
const product = ((activeOffer && activeOffer.products[0]) || null);
|
const product = ((activeOffer && activeOffer.products[0]) || null);
|
||||||
@ -25,7 +25,7 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutDefaultViewProps> = props
|
|||||||
<div className="d-block mb-2">
|
<div className="d-block mb-2">
|
||||||
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
|
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-center text-black lh-sm">{ GetCatalogPageText(pageParser, 0) }</span>
|
<div className="fs-6 text-center text-black lh-sm overflow-hidden">{ GetCatalogPageText(pageParser, 0) }</div>
|
||||||
</div> }
|
</div> }
|
||||||
{ product &&
|
{ product &&
|
||||||
<div className="position-relative d-flex flex-column col">
|
<div className="position-relative d-flex flex-column col">
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
.nitro-catalog-layout-single-bundle {
|
||||||
|
|
||||||
|
.single-bundle-items-container {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { useCatalogContext } from '../../../../context/CatalogContext';
|
||||||
|
import { GetCatalogPageImage, GetCatalogPageText } from '../../../../utils/CatalogUtilities';
|
||||||
|
import { CatalogProductView } from '../../product/CatalogProductView';
|
||||||
|
import { CatalogPurchaseView } from '../../purchase/CatalogPurchaseView';
|
||||||
|
import { CatalogLayoutSingleBundleViewProps } from './CatalogLayoutSingleBundleView.types';
|
||||||
|
|
||||||
|
export const CatalogLayoutSingleBundleView: FC<CatalogLayoutSingleBundleViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { roomPreviewer = null, pageParser = null } = props;
|
||||||
|
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
|
||||||
|
const { activeOffer = null } = catalogState;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row h-100 nitro-catalog-layout-single-bundle">
|
||||||
|
<div className="col-7">
|
||||||
|
<div className="row row-cols-5 align-content-start g-0 mb-n1 w-100 catalog-offers-container single-bundle-items-container">
|
||||||
|
{ activeOffer && activeOffer.products && (activeOffer.products.length > 0) && activeOffer.products.map((product, index) =>
|
||||||
|
{
|
||||||
|
return <CatalogProductView key={ index } isActive={ false } product={ product } />
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="position-relative d-flex flex-column col justify-content-center align-items-center">
|
||||||
|
<div className="d-block mb-2">
|
||||||
|
<img alt="" src={ GetCatalogPageImage(pageParser, 1) } />
|
||||||
|
</div>
|
||||||
|
<div className="fs-6 text-center text-black lh-sm overflow-hidden">{ GetCatalogPageText(pageParser, 0) }</div>
|
||||||
|
{ activeOffer && <CatalogPurchaseView offer={ activeOffer } pageId={ pageParser.pageId } /> }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { CatalogLayoutProps } from '../CatalogLayout.types';
|
||||||
|
|
||||||
|
export interface CatalogLayoutSingleBundleViewProps extends CatalogLayoutProps
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -1,18 +1,13 @@
|
|||||||
import { FurnitureType, MouseEventType } from 'nitro-renderer';
|
import { MouseEventType } from 'nitro-renderer';
|
||||||
import { FC, MouseEvent, useCallback, useState } from 'react';
|
import { FC, MouseEvent, useCallback } from 'react';
|
||||||
import { GetRoomEngine, GetSessionDataManager } from '../../../../../api';
|
|
||||||
import { GetConfiguration } from '../../../../../utils/GetConfiguration';
|
|
||||||
import { AvatarImageView } from '../../../../avatar-image/AvatarImageView';
|
|
||||||
import { LimitedEditionStyledNumberView } from '../../../../limited-edition/styled-number/LimitedEditionStyledNumberView';
|
|
||||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||||
import { ProductTypeEnum } from '../../../enums/ProductTypeEnum';
|
|
||||||
import { CatalogActions } from '../../../reducers/CatalogReducer';
|
import { CatalogActions } from '../../../reducers/CatalogReducer';
|
||||||
|
import { CatalogProductView } from '../product/CatalogProductView';
|
||||||
import { CatalogPageOfferViewProps } from './CatalogPageOfferView.types';
|
import { CatalogPageOfferViewProps } from './CatalogPageOfferView.types';
|
||||||
|
|
||||||
export const CatalogPageOfferView: FC<CatalogPageOfferViewProps> = props =>
|
export const CatalogPageOfferView: FC<CatalogPageOfferViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { isActive = false, offer = null } = props;
|
const { isActive = false, offer = null } = props;
|
||||||
const [ isMouseDown, setMouseDown ] = useState(false);
|
|
||||||
const { dispatchCatalogState = null } = useCatalogContext();
|
const { dispatchCatalogState = null } = useCatalogContext();
|
||||||
|
|
||||||
const onMouseEvent = useCallback((event: MouseEvent) =>
|
const onMouseEvent = useCallback((event: MouseEvent) =>
|
||||||
@ -36,67 +31,5 @@ export const CatalogPageOfferView: FC<CatalogPageOfferViewProps> = props =>
|
|||||||
|
|
||||||
if(!product) return null;
|
if(!product) return null;
|
||||||
|
|
||||||
function getIconUrl(): string
|
return <CatalogProductView isActive={ isActive } product={ product } onMouseEvent={ onMouseEvent } />
|
||||||
{
|
|
||||||
const productType = product.productType.toUpperCase();
|
|
||||||
|
|
||||||
switch(productType)
|
|
||||||
{
|
|
||||||
case FurnitureType.BADGE:
|
|
||||||
return GetSessionDataManager().getBadgeUrl(product.extraParam);
|
|
||||||
case FurnitureType.ROBOT:
|
|
||||||
return null;
|
|
||||||
case FurnitureType.FLOOR:
|
|
||||||
return GetRoomEngine().getFurnitureFloorIconUrl(product.furniClassId);
|
|
||||||
case FurnitureType.WALL: {
|
|
||||||
const furniData = GetSessionDataManager().getWallItemData(product.furniClassId);
|
|
||||||
|
|
||||||
let iconName = '';
|
|
||||||
|
|
||||||
if(furniData)
|
|
||||||
{
|
|
||||||
switch(furniData.className)
|
|
||||||
{
|
|
||||||
case 'floor':
|
|
||||||
iconName = ['th', furniData.className, product.extraParam].join('_');
|
|
||||||
break;
|
|
||||||
case 'wallpaper':
|
|
||||||
iconName = ['th', 'wall', product.extraParam].join('_');
|
|
||||||
break;
|
|
||||||
case 'landscape':
|
|
||||||
iconName = ['th', furniData.className, product.extraParam.replace('.', '_'), '001'].join('_');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(iconName !== '')
|
|
||||||
{
|
|
||||||
const assetUrl = GetConfiguration<string>('catalog.asset.url');
|
|
||||||
|
|
||||||
return `${ assetUrl }/${ iconName }.png`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetRoomEngine().getFurnitureWallIconUrl(product.furniClassId, product.extraParam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const iconUrl = getIconUrl();
|
|
||||||
const imageUrl = (iconUrl && iconUrl.length) ? `url(${ iconUrl })` : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="col pe-1 pb-1 catalog-offer-item-container">
|
|
||||||
<div className={ 'position-relative border border-2 rounded catalog-offer-item cursor-pointer ' + (isActive ? 'active ' : '') + (product.uniqueLimitedItem ? 'unique-item ' : '') + ((product.uniqueLimitedItem && !product.uniqueLimitedItemsLeft) ? 'sold-out ' : '') } style={ { backgroundImage: imageUrl }} onClick={ onMouseEvent }>
|
|
||||||
{ !imageUrl && (product.productType === ProductTypeEnum.ROBOT) &&
|
|
||||||
<AvatarImageView figure={ product.extraParam } direction={ 3 } headOnly={ true } /> }
|
|
||||||
{ (product.productCount > 1) && <span className="position-absolute badge border bg-danger px-1 rounded-circle">{ product.productCount }</span> }
|
|
||||||
{ product.uniqueLimitedItem &&
|
|
||||||
<div className="position-absolute unique-item-counter">
|
|
||||||
<LimitedEditionStyledNumberView value={ product.uniqueLimitedSeriesSize } />
|
|
||||||
</div> }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
80
src/views/catalog/views/page/product/CatalogProductView.tsx
Normal file
80
src/views/catalog/views/page/product/CatalogProductView.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { FurnitureType } from 'nitro-renderer';
|
||||||
|
import { FC, useMemo } from 'react';
|
||||||
|
import { GetRoomEngine, GetSessionDataManager } from '../../../../../api';
|
||||||
|
import { GetConfiguration } from '../../../../../utils/GetConfiguration';
|
||||||
|
import { AvatarImageView } from '../../../../avatar-image/AvatarImageView';
|
||||||
|
import { LimitedEditionStyledNumberView } from '../../../../limited-edition/styled-number/LimitedEditionStyledNumberView';
|
||||||
|
import { ProductTypeEnum } from '../../../enums/ProductTypeEnum';
|
||||||
|
import { CatalogProductViewProps } from './CatalogProductView.types';
|
||||||
|
|
||||||
|
export const CatalogProductView: FC<CatalogProductViewProps> = props =>
|
||||||
|
{
|
||||||
|
const { isActive = false, product = null, onMouseEvent = null } = props;
|
||||||
|
|
||||||
|
const iconUrl = useMemo(() =>
|
||||||
|
{
|
||||||
|
if(!product) return null;
|
||||||
|
|
||||||
|
const productType = product.productType.toUpperCase();
|
||||||
|
|
||||||
|
switch(productType)
|
||||||
|
{
|
||||||
|
case FurnitureType.BADGE:
|
||||||
|
return GetSessionDataManager().getBadgeUrl(product.extraParam);
|
||||||
|
case FurnitureType.ROBOT:
|
||||||
|
return null;
|
||||||
|
case FurnitureType.FLOOR:
|
||||||
|
return GetRoomEngine().getFurnitureFloorIconUrl(product.furniClassId);
|
||||||
|
case FurnitureType.WALL: {
|
||||||
|
const furniData = GetSessionDataManager().getWallItemData(product.furniClassId);
|
||||||
|
|
||||||
|
let iconName = '';
|
||||||
|
|
||||||
|
if(furniData)
|
||||||
|
{
|
||||||
|
switch(furniData.className)
|
||||||
|
{
|
||||||
|
case 'floor':
|
||||||
|
iconName = ['th', furniData.className, product.extraParam].join('_');
|
||||||
|
break;
|
||||||
|
case 'wallpaper':
|
||||||
|
iconName = ['th', 'wall', product.extraParam].join('_');
|
||||||
|
break;
|
||||||
|
case 'landscape':
|
||||||
|
iconName = ['th', furniData.className, product.extraParam.replace('.', '_'), '001'].join('_');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(iconName !== '')
|
||||||
|
{
|
||||||
|
const assetUrl = GetConfiguration<string>('catalog.asset.url');
|
||||||
|
|
||||||
|
return `${ assetUrl }/${ iconName }.png`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetRoomEngine().getFurnitureWallIconUrl(product.furniClassId, product.extraParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [ product ]);
|
||||||
|
|
||||||
|
if(!product) return null;
|
||||||
|
|
||||||
|
const imageUrl = (iconUrl && iconUrl.length) ? `url(${ iconUrl })` : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col pe-1 pb-1 catalog-offer-item-container">
|
||||||
|
<div className={ 'position-relative border border-2 rounded catalog-offer-item cursor-pointer ' + (isActive ? 'active ' : '') + (product.uniqueLimitedItem ? 'unique-item ' : '') + ((product.uniqueLimitedItem && !product.uniqueLimitedItemsLeft) ? 'sold-out ' : '') } style={ { backgroundImage: imageUrl }} onClick={ onMouseEvent }>
|
||||||
|
{ !imageUrl && (product.productType === ProductTypeEnum.ROBOT) &&
|
||||||
|
<AvatarImageView figure={ product.extraParam } direction={ 3 } headOnly={ true } /> }
|
||||||
|
{ (product.productCount > 1) && <span className="position-absolute badge border bg-danger px-1 rounded-circle">{ product.productCount }</span> }
|
||||||
|
{ product.uniqueLimitedItem &&
|
||||||
|
<div className="position-absolute unique-item-counter">
|
||||||
|
<LimitedEditionStyledNumberView value={ product.uniqueLimitedSeriesSize } />
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import { CatalogProductOfferData } from 'nitro-renderer';
|
||||||
|
import { MouseEventHandler } from 'react';
|
||||||
|
|
||||||
|
export interface CatalogProductViewProps
|
||||||
|
{
|
||||||
|
isActive: boolean;
|
||||||
|
product: CatalogProductOfferData;
|
||||||
|
onMouseEvent?: MouseEventHandler<Element>;
|
||||||
|
}
|
@ -44,7 +44,7 @@ export const CatalogPurchaseView: FC<CatalogPurchaseViewProps> = props =>
|
|||||||
const extraData = ((extra && extra.length) ? extra : (offer?.products[0]?.extraParam || null));
|
const extraData = ((extra && extra.length) ? extra : (offer?.products[0]?.extraParam || null));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="d-flex flex-column flex-grow-1 justify-content-end">
|
<div className="d-flex flex-column flex-grow-1 justify-content-end w-100">
|
||||||
<div className="d-flex align-items-end">
|
<div className="d-flex align-items-end">
|
||||||
<div className="flex-grow-1 align-items-end">
|
<div className="flex-grow-1 align-items-end">
|
||||||
<span className="text-black">{ LocalizeText('catalog.bundlewidget.price') }</span>
|
<span className="text-black">{ LocalizeText('catalog.bundlewidget.price') }</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user