mirror of
https://github.com/billsonnn/nitro-react.git
synced 2025-01-31 10:22:36 +01:00
More catalog changes
This commit is contained in:
parent
c19810327d
commit
8129bd3393
@ -2,7 +2,7 @@ import { ApproveNameMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent
|
||||
import { GuildMembershipsMessageEvent } from '@nitrots/nitro-renderer/src/nitro/communication/messages/incoming/user/GuildMembershipsMessageEvent';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { GetFurnitureData, GetProductDataForLocalization, LocalizeText } from '../../api';
|
||||
import { CatalogNameResultEvent, CatalogPurchaseFailureEvent, CatalogSelectProductEvent, CatalogSetExtraPurchaseParameterEvent } from '../../events';
|
||||
import { CatalogNameResultEvent, CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogSetExtraPurchaseParameterEvent } from '../../events';
|
||||
import { CatalogGiftReceiverNotFoundEvent } from '../../events/catalog/CatalogGiftReceiverNotFoundEvent';
|
||||
import { CatalogPurchasedEvent } from '../../events/catalog/CatalogPurchasedEvent';
|
||||
import { CatalogPurchaseSoldOutEvent } from '../../events/catalog/CatalogPurchaseSoldOutEvent';
|
||||
@ -14,6 +14,7 @@ import { NotificationUtilities } from '../../views/notification-center/common/No
|
||||
import { CatalogNode } from './common/CatalogNode';
|
||||
import { CatalogPetPalette } from './common/CatalogPetPalette';
|
||||
import { CatalogType } from './common/CatalogType';
|
||||
import { GiftWrappingConfiguration } from './common/GiftWrappingConfiguration';
|
||||
import { ICatalogNode } from './common/ICatalogNode';
|
||||
import { IProduct } from './common/IProduct';
|
||||
import { IPurchasableOffer } from './common/IPurchasableOffer';
|
||||
@ -23,11 +24,10 @@ import { Product } from './common/Product';
|
||||
import { ProductTypeEnum } from './common/ProductTypeEnum';
|
||||
import { SubscriptionInfo } from './common/SubscriptionInfo';
|
||||
import { useCatalogContext } from './context/CatalogContext';
|
||||
import { CatalogActions } from './reducers/CatalogReducer';
|
||||
|
||||
export const CatalogMessageHandler: FC<{}> = props =>
|
||||
{
|
||||
const { setIsBusy, pageId, currentType, setRootNode, setOffersToNodes, currentPage, setCurrentOffer, setPurchasableOffer, setFrontPageItems, resetState, showCatalogPage, catalogState, dispatchCatalogState } = useCatalogContext();
|
||||
const { setIsBusy, pageId, currentType, setRootNode, setOffersToNodes, currentPage, setCurrentOffer, setFrontPageItems, resetState, showCatalogPage, setCatalogOptions = null } = useCatalogContext();
|
||||
|
||||
const onCatalogPagesListEvent = useCallback((event: CatalogPagesListEvent) =>
|
||||
{
|
||||
@ -115,6 +115,8 @@ export const CatalogMessageHandler: FC<{}> = props =>
|
||||
const onPurchaseNotAllowedMessageEvent = useCallback((event: PurchaseNotAllowedMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
dispatchUiEvent(new CatalogPurchaseNotAllowedEvent(parser.code));
|
||||
}, []);
|
||||
|
||||
const onLimitedEditionSoldOutEvent = useCallback((event: LimitedEditionSoldOutEvent) =>
|
||||
@ -154,7 +156,7 @@ export const CatalogMessageHandler: FC<{}> = props =>
|
||||
|
||||
offer.page = currentPage;
|
||||
|
||||
dispatchUiEvent(new CatalogSelectProductEvent(offer));
|
||||
setCurrentOffer(offer);
|
||||
|
||||
if(offer.product && (offer.product.productType === ProductTypeEnum.WALL))
|
||||
{
|
||||
@ -162,19 +164,36 @@ export const CatalogMessageHandler: FC<{}> = props =>
|
||||
}
|
||||
|
||||
// (this._isObjectMoverRequested) && (this._purchasableOffer)
|
||||
// setPurchasableOffer(offer);
|
||||
}, [ currentType, currentPage ]);
|
||||
}, [ currentType, currentPage, setCurrentOffer ]);
|
||||
|
||||
const onSellablePetPalettesMessageEvent = useCallback((event: SellablePetPalettesMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
const petPalette = new CatalogPetPalette(parser.productCode, parser.palettes.slice());
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_PET_PALETTE,
|
||||
payload: { petPalette }
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
const petPalettes = [];
|
||||
|
||||
if(prevValue.petPalettes) petPalettes.push(...prevValue.petPalettes);
|
||||
|
||||
for(let i = 0; i < petPalettes.length; i++)
|
||||
{
|
||||
const palette = petPalettes[i];
|
||||
|
||||
if(palette.breed === petPalette.breed)
|
||||
{
|
||||
petPalettes.splice(i, 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
petPalettes.push(petPalette);
|
||||
|
||||
return { ...prevValue, petPalettes };
|
||||
});
|
||||
}, [ dispatchCatalogState ]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
const onApproveNameMessageEvent = useCallback((event: ApproveNameMessageEvent) =>
|
||||
{
|
||||
@ -192,43 +211,42 @@ export const CatalogMessageHandler: FC<{}> = props =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_CLUB_OFFERS,
|
||||
payload: {
|
||||
clubOffers: parser.offers
|
||||
}
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
const clubOffers = parser.offers;
|
||||
|
||||
return { ...prevValue, clubOffers };
|
||||
});
|
||||
}, [ dispatchCatalogState ]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
const onGuildMembershipsMessageEvent = useCallback((event: GuildMembershipsMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_GROUPS,
|
||||
payload: {
|
||||
groups: parser.groups
|
||||
}
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
const groups = parser.groups;
|
||||
|
||||
return { ...prevValue, groups };
|
||||
});
|
||||
}, [ dispatchCatalogState ]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
const onUserSubscriptionEvent = useCallback((event: UserSubscriptionEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_SUBSCRIPTION_INFO,
|
||||
payload: {
|
||||
subscriptionInfo: new SubscriptionInfo(
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
const subscriptionInfo = new SubscriptionInfo(
|
||||
Math.max(0, parser.daysToPeriodEnd),
|
||||
Math.max(0, parser.periodsSubscribedAhead),
|
||||
parser.isVip,
|
||||
parser.pastClubDays,
|
||||
parser.pastVipDays
|
||||
)
|
||||
}
|
||||
parser.pastVipDays);
|
||||
|
||||
return { ...prevValue, subscriptionInfo };
|
||||
});
|
||||
}, [ dispatchCatalogState ]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
const onCatalogPublishedMessageEvent = useCallback((event: CatalogPublishedMessageEvent) =>
|
||||
{
|
||||
@ -239,13 +257,13 @@ export const CatalogMessageHandler: FC<{}> = props =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_GIFT_CONFIGURATION,
|
||||
payload: {
|
||||
giftConfiguration: parser
|
||||
}
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
const giftConfiguration = new GiftWrappingConfiguration(parser);
|
||||
|
||||
return { ...prevValue, giftConfiguration };
|
||||
});
|
||||
}, [ dispatchCatalogState ]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
const onMarketplaceMakeOfferResult = useCallback((event: MarketplaceMakeOfferResult) =>
|
||||
{
|
||||
@ -272,29 +290,25 @@ export const CatalogMessageHandler: FC<{}> = props =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser) return;
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
const marketplaceConfiguration = parser;
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_MARKETPLACE_CONFIGURATION,
|
||||
payload: {
|
||||
marketplaceConfiguration: parser
|
||||
}
|
||||
return { ...prevValue, marketplaceConfiguration };
|
||||
});
|
||||
}, [dispatchCatalogState]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
const onClubGiftInfoEvent = useCallback((event: ClubGiftInfoEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser) return;
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
const clubGifts = parser;
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_CLUB_GIFTS,
|
||||
payload: {
|
||||
clubGifts: parser
|
||||
}
|
||||
return { ...prevValue, clubGifts };
|
||||
});
|
||||
}, [dispatchCatalogState]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
CreateMessageHook(CatalogPagesListEvent, onCatalogPagesListEvent);
|
||||
CreateMessageHook(CatalogPageMessageEvent, onCatalogPageMessageEvent);
|
||||
|
@ -5,6 +5,11 @@
|
||||
font[size="16"] {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.catalog-search-button {
|
||||
min-width: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-catalog-navigation-grid-container {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { FrontPageItem, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetGiftWrappingConfigurationComposer, GetMarketplaceConfigurationMessageComposer, ILinkEventTracker, RoomPreviewer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { AddEventLinkTracker, GetRoomEngine, LocalizeText, RemoveLinkEventTracker } from '../../api';
|
||||
import { CREDITS, PlaySound } from '../../api/utils/PlaySound';
|
||||
import { Column } from '../../common/Column';
|
||||
import { Grid } from '../../common/Grid';
|
||||
import { CatalogEvent } from '../../events';
|
||||
import { CatalogPurchasedEvent } from '../../events';
|
||||
import { BatchUpdates } from '../../hooks';
|
||||
import { useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||
import { SendMessageHook } from '../../hooks/messages/message-event';
|
||||
@ -13,27 +13,25 @@ import { CatalogMessageHandler } from './CatalogMessageHandler';
|
||||
import { CatalogPage } from './common/CatalogPage';
|
||||
import { CatalogType } from './common/CatalogType';
|
||||
import { ICatalogNode } from './common/ICatalogNode';
|
||||
import { ICatalogOptions } from './common/ICatalogOptions';
|
||||
import { ICatalogPage } from './common/ICatalogPage';
|
||||
import { IPageLocalization } from './common/IPageLocalization';
|
||||
import { IPurchasableOffer } from './common/IPurchasableOffer';
|
||||
import { IPurchaseOptions } from './common/IPurchaseOptions';
|
||||
import { RequestedPage } from './common/RequestedPage';
|
||||
import { SearchResult } from './common/SearchResult';
|
||||
import { CatalogContextProvider } from './context/CatalogContext';
|
||||
import { CatalogReducer, initialCatalog } from './reducers/CatalogReducer';
|
||||
import { CatalogGiftView } from './views/gift/CatalogGiftView';
|
||||
import { CatalogNavigationView } from './views/navigation/CatalogNavigationView';
|
||||
import { CatalogPageView } from './views/page/CatalogPageView';
|
||||
import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout';
|
||||
import { MarketplacePostOfferView } from './views/page/layout/marketplace/MarketplacePostOfferView';
|
||||
|
||||
const DUMMY_PAGE_ID_FOR_OFFER_SEARCH: number = -12345678;
|
||||
const REQUESTED_PAGE = new RequestedPage();
|
||||
|
||||
export const CatalogView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
|
||||
const [ isBusy, setIsBusy ] = useState(false);
|
||||
const [ forceRefresh, setForceRefresh ] = useState(false);
|
||||
const [ pageId, setPageId ] = useState(-1);
|
||||
const [ previousPageId, setPreviousPageId ] = useState(-1);
|
||||
const [ currentType, setCurrentType ] = useState(CatalogType.NORMAL);
|
||||
@ -41,12 +39,12 @@ export const CatalogView: FC<{}> = props =>
|
||||
const [ offersToNodes, setOffersToNodes ] = useState<Map<number, ICatalogNode[]>>(null);
|
||||
const [ currentPage, setCurrentPage ] = useState<ICatalogPage>(null);
|
||||
const [ currentOffer, setCurrentOffer ] = useState<IPurchasableOffer>(null);
|
||||
const [ purchasableOffer, setPurchasableOffer ] = useState<IPurchasableOffer>(null);
|
||||
const [ activeNodes, setActiveNodes ] = useState<ICatalogNode[]>([]);
|
||||
const [ searchResult, setSearchResult ] = useState<SearchResult>(null);
|
||||
const [ frontPageItems, setFrontPageItems ] = useState<FrontPageItem[]>([]);
|
||||
const [ roomPreviewer, setRoomPreviewer ] = useState<RoomPreviewer>(null);
|
||||
const [ catalogState, dispatchCatalogState ] = useReducer(CatalogReducer, initialCatalog);
|
||||
const [ purchaseOptions, setPurchaseOptions ] = useState<IPurchaseOptions>({});
|
||||
const [ catalogOptions, setCatalogOptions ] = useState<ICatalogOptions>({});
|
||||
|
||||
const resetState = useCallback(() =>
|
||||
{
|
||||
@ -58,7 +56,6 @@ export const CatalogView: FC<{}> = props =>
|
||||
setOffersToNodes(null);
|
||||
setCurrentPage(null);
|
||||
setCurrentOffer(null);
|
||||
setPurchasableOffer(null);
|
||||
setActiveNodes([]);
|
||||
setSearchResult(null);
|
||||
setFrontPageItems([]);
|
||||
@ -66,13 +63,27 @@ export const CatalogView: FC<{}> = props =>
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getFirstNodeByName = useCallback((name: string, node: ICatalogNode) =>
|
||||
const getNodeById = useCallback((id: number, node: ICatalogNode) =>
|
||||
{
|
||||
if((node.pageId === id) && (node !== rootNode)) return node;
|
||||
|
||||
for(const child of node.children)
|
||||
{
|
||||
const found = (getNodeById(id, child) as ICatalogNode);
|
||||
|
||||
if(found) return found;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [ rootNode ]);
|
||||
|
||||
const getNodeByName = useCallback((name: string, node: ICatalogNode) =>
|
||||
{
|
||||
if((node.pageName === name) && (node !== rootNode)) return node;
|
||||
|
||||
for(const child of node.children)
|
||||
{
|
||||
const found = (getFirstNodeByName(name, child) as ICatalogNode);
|
||||
const found = (getNodeByName(name, child) as ICatalogNode);
|
||||
|
||||
if(found) return found;
|
||||
}
|
||||
@ -110,43 +121,28 @@ export const CatalogView: FC<{}> = props =>
|
||||
if(pageId > -1) SendMessageHook(new GetCatalogPageComposer(pageId, offerId, currentType));
|
||||
}, [ currentType ]);
|
||||
|
||||
const selectOffer = useCallback((offerId: number) =>
|
||||
{
|
||||
if(!currentPage || !currentPage.offers || offerId < 0) return;
|
||||
|
||||
for(const offer of currentPage.offers)
|
||||
{
|
||||
if(offer.offerId !== offerId) continue;
|
||||
|
||||
setCurrentOffer(offer)
|
||||
|
||||
return;
|
||||
}
|
||||
}, [ currentPage ]);
|
||||
|
||||
const showCatalogPage = useCallback((pageId: number, layoutCode: string, localization: IPageLocalization, offers: IPurchasableOffer[], offerId: number, acceptSeasonCurrencyAsCredits: boolean) =>
|
||||
{
|
||||
if(currentPage)
|
||||
{
|
||||
if(!forceRefresh && (currentPage.pageId === pageId))
|
||||
{
|
||||
if(offerId > -1) selectOffer(offerId);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const catalogPage = (new CatalogPage(pageId, layoutCode, localization, offers, acceptSeasonCurrencyAsCredits) as ICatalogPage);
|
||||
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setCurrentPage(catalogPage);
|
||||
setPreviousPageId(prevValue => ((pageId > DUMMY_PAGE_ID_FOR_OFFER_SEARCH) ? pageId : prevValue));
|
||||
setForceRefresh(false);
|
||||
setPreviousPageId(prevValue => ((pageId !== -1) ? pageId : prevValue));
|
||||
|
||||
selectOffer(offerId);
|
||||
if((offerId > -1) && catalogPage.offers.length)
|
||||
{
|
||||
for(const offer of catalogPage.offers)
|
||||
{
|
||||
if(offer.offerId !== offerId) continue;
|
||||
|
||||
setCurrentOffer(offer)
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [ currentPage, forceRefresh, selectOffer ]);
|
||||
}, []);
|
||||
|
||||
const activateNode = useCallback((targetNode: ICatalogNode, offerId: number = -1) =>
|
||||
{
|
||||
@ -169,7 +165,7 @@ export const CatalogView: FC<{}> = props =>
|
||||
|
||||
let node = targetNode;
|
||||
|
||||
while(node && node.pageName !== 'root')
|
||||
while(node && (node.pageName !== 'root'))
|
||||
{
|
||||
nodes.push(node);
|
||||
|
||||
@ -194,7 +190,9 @@ export const CatalogView: FC<{}> = props =>
|
||||
{
|
||||
n.activate();
|
||||
|
||||
if(n === targetNode.parent) n.open();
|
||||
if(n.parent) n.open();
|
||||
|
||||
if((n === targetNode.parent) && n.children.length) n.open();
|
||||
}
|
||||
|
||||
if(isActive && isOpen) targetNode.close();
|
||||
@ -206,6 +204,27 @@ export const CatalogView: FC<{}> = props =>
|
||||
if(targetNode.pageId > -1) loadCatalogPage(targetNode.pageId, offerId);
|
||||
}, [ setActiveNodes, loadCatalogPage ]);
|
||||
|
||||
const openPageById = useCallback((id: number) =>
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setSearchResult(null);
|
||||
|
||||
if(!isVisible)
|
||||
{
|
||||
REQUESTED_PAGE.requestById = id;
|
||||
|
||||
setIsVisible(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
const node = getNodeById(id, rootNode);
|
||||
|
||||
if(node) activateNode(node);
|
||||
}
|
||||
});
|
||||
}, [ isVisible, rootNode, getNodeById, activateNode ]);
|
||||
|
||||
const openPageByName = useCallback((name: string) =>
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
@ -220,12 +239,12 @@ export const CatalogView: FC<{}> = props =>
|
||||
}
|
||||
else
|
||||
{
|
||||
const node = getFirstNodeByName(name, rootNode);
|
||||
const node = getNodeByName(name, rootNode);
|
||||
|
||||
if(node) activateNode(node);
|
||||
}
|
||||
});
|
||||
}, [ isVisible, rootNode, getFirstNodeByName, activateNode ]);
|
||||
}, [ isVisible, rootNode, getNodeByName, activateNode ]);
|
||||
|
||||
const openPageByOfferId = useCallback((offerId: number) =>
|
||||
{
|
||||
@ -250,17 +269,12 @@ export const CatalogView: FC<{}> = props =>
|
||||
});
|
||||
}, [ isVisible, getNodesByOfferId, activateNode ]);
|
||||
|
||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||
const onCatalogPurchasedEvent = useCallback((event: CatalogPurchasedEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case CatalogEvent.PURCHASE_SUCCESS:
|
||||
PlaySound(CREDITS);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogPurchasedEvent);
|
||||
|
||||
const linkReceived = useCallback((url: string) =>
|
||||
{
|
||||
@ -294,8 +308,6 @@ export const CatalogView: FC<{}> = props =>
|
||||
else
|
||||
{
|
||||
openPageByName(parts[2]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -367,6 +379,7 @@ export const CatalogView: FC<{}> = props =>
|
||||
}
|
||||
return;
|
||||
case RequestedPage.REQUEST_TYPE_ID:
|
||||
openPageById(REQUESTED_PAGE.requestById);
|
||||
REQUESTED_PAGE.resetRequest();
|
||||
return;
|
||||
case RequestedPage.REQUEST_TYPE_OFFER:
|
||||
@ -378,17 +391,20 @@ export const CatalogView: FC<{}> = props =>
|
||||
REQUESTED_PAGE.resetRequest();
|
||||
return;
|
||||
}
|
||||
}, [ isVisible, rootNode, activeNodes, activateNode, openPageByOfferId, openPageByName ]);
|
||||
}, [ isVisible, rootNode, activeNodes, activateNode, openPageById, openPageByOfferId, openPageByName ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentPage) return;
|
||||
if(!searchResult && currentPage && (currentPage.pageId === -1)) openPageById(previousPageId);
|
||||
}, [ searchResult, currentPage, previousPageId, openPageById ]);
|
||||
|
||||
setCurrentOffer(null);
|
||||
useEffect(() =>
|
||||
{
|
||||
return () => setCurrentOffer(null);
|
||||
}, [ currentPage ]);
|
||||
|
||||
return (
|
||||
<CatalogContextProvider value={ { isVisible, isBusy, setIsBusy, pageId, currentType, setCurrentType, rootNode, setRootNode, offersToNodes, setOffersToNodes, currentPage, setCurrentPage, currentOffer, setCurrentOffer, purchasableOffer, setPurchasableOffer, activeNodes, setActiveNodes, searchResult, setSearchResult, frontPageItems, setFrontPageItems, roomPreviewer, resetState, loadCatalogPage, showCatalogPage, activateNode, catalogState, dispatchCatalogState } }>
|
||||
<CatalogContextProvider value={ { isVisible, isBusy, setIsBusy, pageId, currentType, setCurrentType, rootNode, setRootNode, offersToNodes, setOffersToNodes, currentPage, setCurrentPage, currentOffer, setCurrentOffer, activeNodes, setActiveNodes, searchResult, setSearchResult, frontPageItems, setFrontPageItems, roomPreviewer, purchaseOptions, setPurchaseOptions, catalogOptions, setCatalogOptions, resetState, getNodesByOfferId, loadCatalogPage, showCatalogPage, activateNode } }>
|
||||
<CatalogMessageHandler />
|
||||
{ isVisible &&
|
||||
<NitroCardView uniqueKey="catalog" className="nitro-catalog">
|
||||
@ -417,7 +433,7 @@ export const CatalogView: FC<{}> = props =>
|
||||
<CatalogNavigationView node={ activeNodes[0] } /> }
|
||||
</Column>
|
||||
<Column size={ 9 } overflow="hidden">
|
||||
<CatalogPageView page={ currentPage } roomPreviewer={ roomPreviewer } />
|
||||
{ GetCatalogLayout(currentPage) }
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
|
@ -12,41 +12,6 @@ export const GetSubscriptionProductIcon = (id: number) =>
|
||||
return '';
|
||||
}
|
||||
|
||||
export const GetNodeById = (id: number, searchNode: ICatalogNode = null, rootNode: ICatalogNode) =>
|
||||
{
|
||||
if(!searchNode) searchNode = rootNode;
|
||||
|
||||
if(!searchNode) return null;
|
||||
|
||||
if((searchNode.pageId === id) && (searchNode !== rootNode)) return searchNode;
|
||||
|
||||
for(const child of searchNode.children)
|
||||
{
|
||||
const node = (GetNodeById(id, child, rootNode) as ICatalogNode);
|
||||
|
||||
if(node) return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const GetNodesByOfferId = (offerId: number, flag: boolean = false, currentOffers: Map<number, ICatalogNode[]>) =>
|
||||
{
|
||||
if(!currentOffers || !currentOffers.size) return null;
|
||||
|
||||
if(flag)
|
||||
{
|
||||
const nodes: ICatalogNode[] = [];
|
||||
const offers = currentOffers.get(offerId);
|
||||
|
||||
if(offers && offers.length) for(const offer of offers) (offer.isVisible && nodes.push(offer));
|
||||
|
||||
if(nodes.length) return nodes;
|
||||
}
|
||||
|
||||
return currentOffers.get(offerId);
|
||||
}
|
||||
|
||||
export const GetOfferNodes = (offerNodes: Map<number, ICatalogNode[]>, offerId: number) =>
|
||||
{
|
||||
const nodes = offerNodes.get(offerId);
|
||||
|
15
src/components/catalog/common/ICatalogOptions.ts
Normal file
15
src/components/catalog/common/ICatalogOptions.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { ClubGiftInfoParser, ClubOfferData, HabboGroupEntryData, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer';
|
||||
import { CatalogPetPalette } from './CatalogPetPalette';
|
||||
import { GiftWrappingConfiguration } from './GiftWrappingConfiguration';
|
||||
import { SubscriptionInfo } from './SubscriptionInfo';
|
||||
|
||||
export interface ICatalogOptions
|
||||
{
|
||||
groups?: HabboGroupEntryData[];
|
||||
petPalettes?: CatalogPetPalette[];
|
||||
clubOffers?: ClubOfferData[];
|
||||
clubGifts?: ClubGiftInfoParser;
|
||||
subscriptionInfo?: SubscriptionInfo;
|
||||
giftConfiguration?: GiftWrappingConfiguration;
|
||||
marketplaceConfiguration?: MarketplaceConfigurationMessageParser;
|
||||
}
|
@ -4,13 +4,13 @@ import { IPurchasableOffer } from './IPurchasableOffer';
|
||||
export interface IProduct
|
||||
{
|
||||
getIconUrl(offer?: IPurchasableOffer): string;
|
||||
readonly productType: string;
|
||||
readonly productClassId: number;
|
||||
readonly extraParam: string;
|
||||
readonly productCount: number;
|
||||
readonly productData: IProductData;
|
||||
readonly furnitureData: IFurnitureData;
|
||||
readonly isUniqueLimitedItem: boolean;
|
||||
readonly uniqueLimitedItemSeriesSize: number;
|
||||
productType: string;
|
||||
productClassId: number;
|
||||
extraParam: string;
|
||||
productCount: number;
|
||||
productData: IProductData;
|
||||
furnitureData: IFurnitureData;
|
||||
isUniqueLimitedItem: boolean;
|
||||
uniqueLimitedItemSeriesSize: number;
|
||||
uniqueLimitedItemsLeft: number;
|
||||
}
|
||||
|
@ -3,21 +3,21 @@ import { IProduct } from './IProduct';
|
||||
|
||||
export interface IPurchasableOffer
|
||||
{
|
||||
readonly clubLevel: number;
|
||||
clubLevel: number;
|
||||
page: ICatalogPage;
|
||||
readonly offerId: number;
|
||||
readonly localizationId: string;
|
||||
readonly priceInCredits: number;
|
||||
readonly priceInActivityPoints: number;
|
||||
readonly activityPointType: number;
|
||||
readonly giftable: boolean;
|
||||
readonly product: IProduct;
|
||||
readonly pricingModel: string;
|
||||
readonly priceType: string;
|
||||
readonly bundlePurchaseAllowed: boolean;
|
||||
readonly isRentOffer: boolean;
|
||||
readonly badgeCode: string;
|
||||
readonly localizationName: string;
|
||||
readonly localizationDescription: string;
|
||||
readonly products: IProduct[];
|
||||
offerId: number;
|
||||
localizationId: string;
|
||||
priceInCredits: number;
|
||||
priceInActivityPoints: number;
|
||||
activityPointType: number;
|
||||
giftable: boolean;
|
||||
product: IProduct;
|
||||
pricingModel: string;
|
||||
priceType: string;
|
||||
bundlePurchaseAllowed: boolean;
|
||||
isRentOffer: boolean;
|
||||
badgeCode: string;
|
||||
localizationName: string;
|
||||
localizationDescription: string;
|
||||
products: IProduct[];
|
||||
}
|
||||
|
10
src/components/catalog/common/IPurchaseOptions.ts
Normal file
10
src/components/catalog/common/IPurchaseOptions.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { IObjectData } from '@nitrots/nitro-renderer';
|
||||
|
||||
export interface IPurchaseOptions
|
||||
{
|
||||
quantity?: number;
|
||||
extraData?: string;
|
||||
extraParamRequired?: boolean;
|
||||
purchaseCallback?: Function;
|
||||
previewStuffData?: IObjectData;
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import { FrontPageItem, RoomPreviewer } from '@nitrots/nitro-renderer';
|
||||
import { createContext, Dispatch, FC, ProviderProps, SetStateAction, useContext } from 'react';
|
||||
import { ICatalogNode } from '../common/ICatalogNode';
|
||||
import { ICatalogOptions } from '../common/ICatalogOptions';
|
||||
import { ICatalogPage } from '../common/ICatalogPage';
|
||||
import { IPageLocalization } from '../common/IPageLocalization';
|
||||
import { IPurchasableOffer } from '../common/IPurchasableOffer';
|
||||
import { IPurchaseOptions } from '../common/IPurchaseOptions';
|
||||
import { SearchResult } from '../common/SearchResult';
|
||||
import { ICatalogAction, ICatalogState } from '../reducers/CatalogReducer';
|
||||
|
||||
export interface ICatalogContext
|
||||
interface ICatalogContext
|
||||
{
|
||||
isVisible: boolean;
|
||||
isBusy: boolean;
|
||||
@ -23,8 +24,6 @@ export interface ICatalogContext
|
||||
setCurrentPage: Dispatch<SetStateAction<ICatalogPage>>;
|
||||
currentOffer: IPurchasableOffer;
|
||||
setCurrentOffer: Dispatch<SetStateAction<IPurchasableOffer>>;
|
||||
purchasableOffer: IPurchasableOffer;
|
||||
setPurchasableOffer: Dispatch<SetStateAction<IPurchasableOffer>>;
|
||||
activeNodes: ICatalogNode[];
|
||||
setActiveNodes: Dispatch<SetStateAction<ICatalogNode[]>>;
|
||||
searchResult: SearchResult;
|
||||
@ -32,13 +31,15 @@ export interface ICatalogContext
|
||||
frontPageItems: FrontPageItem[];
|
||||
setFrontPageItems: Dispatch<SetStateAction<FrontPageItem[]>>;
|
||||
roomPreviewer: RoomPreviewer;
|
||||
purchaseOptions: IPurchaseOptions;
|
||||
setPurchaseOptions: Dispatch<SetStateAction<IPurchaseOptions>>;
|
||||
catalogOptions: ICatalogOptions;
|
||||
setCatalogOptions: Dispatch<SetStateAction<ICatalogOptions>>;
|
||||
resetState: () => void;
|
||||
getNodesByOfferId: (offerId: number, flag?: boolean) => ICatalogNode[];
|
||||
loadCatalogPage: (pageId: number, offerId: number) => void;
|
||||
showCatalogPage: (pageId: number, layoutCode: string, localization: IPageLocalization, offers: IPurchasableOffer[], offerId: number, acceptSeasonCurrencyAsCredits: boolean) => void;
|
||||
activateNode: (targetNode: ICatalogNode) => void;
|
||||
|
||||
catalogState: ICatalogState;
|
||||
dispatchCatalogState: Dispatch<ICatalogAction>;
|
||||
}
|
||||
|
||||
const CatalogContext = createContext<ICatalogContext>({
|
||||
@ -56,8 +57,6 @@ const CatalogContext = createContext<ICatalogContext>({
|
||||
setCurrentPage: null,
|
||||
currentOffer: null,
|
||||
setCurrentOffer: null,
|
||||
purchasableOffer: null,
|
||||
setPurchasableOffer: null,
|
||||
activeNodes: null,
|
||||
setActiveNodes: null,
|
||||
searchResult: null,
|
||||
@ -65,12 +64,15 @@ const CatalogContext = createContext<ICatalogContext>({
|
||||
frontPageItems: null,
|
||||
setFrontPageItems: null,
|
||||
roomPreviewer: null,
|
||||
purchaseOptions: null,
|
||||
setPurchaseOptions: null,
|
||||
catalogOptions: null,
|
||||
setCatalogOptions: null,
|
||||
resetState: null,
|
||||
getNodesByOfferId: null,
|
||||
loadCatalogPage: null,
|
||||
showCatalogPage: null,
|
||||
activateNode: null,
|
||||
catalogState: null,
|
||||
dispatchCatalogState: null
|
||||
activateNode: null
|
||||
});
|
||||
|
||||
export const CatalogContextProvider: FC<ProviderProps<ICatalogContext>> = props =>
|
||||
|
@ -1,117 +0,0 @@
|
||||
import { ClubGiftInfoParser, ClubOfferData, GiftWrappingConfigurationParser, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer';
|
||||
import { HabboGroupEntryData } from '@nitrots/nitro-renderer/src/nitro/communication/messages/parser/user/HabboGroupEntryData';
|
||||
import { Reducer } from 'react';
|
||||
import { CatalogPetPalette } from '../common/CatalogPetPalette';
|
||||
import { GiftWrappingConfiguration } from '../common/GiftWrappingConfiguration';
|
||||
import { SubscriptionInfo } from '../common/SubscriptionInfo';
|
||||
|
||||
export interface ICatalogState
|
||||
{
|
||||
groups: HabboGroupEntryData[];
|
||||
petPalettes: CatalogPetPalette[];
|
||||
clubOffers: ClubOfferData[];
|
||||
clubGifts: ClubGiftInfoParser;
|
||||
subscriptionInfo: SubscriptionInfo;
|
||||
giftConfiguration: GiftWrappingConfiguration;
|
||||
marketplaceConfiguration: MarketplaceConfigurationMessageParser;
|
||||
}
|
||||
|
||||
export interface ICatalogAction
|
||||
{
|
||||
type: string;
|
||||
payload: {
|
||||
groups?: HabboGroupEntryData[];
|
||||
petPalette?: CatalogPetPalette;
|
||||
clubOffers?: ClubOfferData[];
|
||||
clubGifts?: ClubGiftInfoParser;
|
||||
subscriptionInfo?: SubscriptionInfo;
|
||||
giftConfiguration?: GiftWrappingConfigurationParser;
|
||||
marketplaceConfiguration?: MarketplaceConfigurationMessageParser;
|
||||
}
|
||||
}
|
||||
|
||||
export class CatalogActions
|
||||
{
|
||||
public static RESET_STATE: string = 'CA_RESET_STATE';
|
||||
public static SET_CLUB_OFFERS: string = 'CA_SET_CLUB_OFFERS';
|
||||
public static SET_GROUPS: string = 'CA_SET_GROUPS';
|
||||
public static SET_PET_PALETTE: string = 'CA_SET_PET_PALETTE';
|
||||
public static SET_SEARCH_RESULT: string = 'CA_SET_SEARCH_RESULT';
|
||||
public static SET_SUBSCRIPTION_INFO: string = 'CA_SET_SUBSCRIPTION_INFO';
|
||||
public static SET_GIFT_CONFIGURATION: string = 'CA_SET_GIFT_CONFIGURATION';
|
||||
public static SET_CLUB_GIFTS: string = 'CA_SET_CLUB_GIFTS';
|
||||
public static SET_MARKETPLACE_CONFIGURATION: string = 'CA_SET_MARKETPLACE_CONFIGURATION';
|
||||
}
|
||||
|
||||
export const initialCatalog: ICatalogState = {
|
||||
groups: [],
|
||||
petPalettes: [],
|
||||
clubOffers: null,
|
||||
clubGifts: null,
|
||||
subscriptionInfo: new SubscriptionInfo(),
|
||||
giftConfiguration: null,
|
||||
marketplaceConfiguration: null
|
||||
}
|
||||
|
||||
export const CatalogReducer: Reducer<ICatalogState, ICatalogAction> = (state, action) =>
|
||||
{
|
||||
switch(action.type)
|
||||
{
|
||||
case CatalogActions.SET_GROUPS: {
|
||||
const groups = (action.payload.groups || null);
|
||||
|
||||
return { ...state, groups };
|
||||
}
|
||||
case CatalogActions.SET_PET_PALETTE: {
|
||||
const petPalette = (action.payload.petPalette || null);
|
||||
|
||||
let petPalettes = [ ...state.petPalettes ];
|
||||
|
||||
for(let i = 0; i < petPalettes.length; i++)
|
||||
{
|
||||
const palette = petPalettes[i];
|
||||
|
||||
if(palette.breed === petPalette.breed)
|
||||
{
|
||||
petPalettes.splice(i, 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
petPalettes.push(petPalette);
|
||||
|
||||
return { ...state, petPalettes };
|
||||
}
|
||||
case CatalogActions.SET_CLUB_OFFERS: {
|
||||
const clubOffers = (action.payload.clubOffers || null);
|
||||
|
||||
return { ...state, clubOffers };
|
||||
}
|
||||
case CatalogActions.SET_SUBSCRIPTION_INFO: {
|
||||
const subscriptionInfo = (action.payload.subscriptionInfo || null);
|
||||
|
||||
return { ...state, subscriptionInfo };
|
||||
}
|
||||
case CatalogActions.RESET_STATE: {
|
||||
return { ...initialCatalog };
|
||||
}
|
||||
case CatalogActions.SET_GIFT_CONFIGURATION: {
|
||||
const giftConfiguration = new GiftWrappingConfiguration((action.payload.giftConfiguration || null));
|
||||
|
||||
return { ...state, giftConfiguration };
|
||||
}
|
||||
case CatalogActions.SET_CLUB_GIFTS: {
|
||||
const clubGifts = (action.payload.clubGifts || state.clubGifts || null);
|
||||
|
||||
return { ...state, clubGifts };
|
||||
}
|
||||
case CatalogActions.SET_MARKETPLACE_CONFIGURATION: {
|
||||
const marketplaceConfiguration = (action.payload.marketplaceConfiguration || state.marketplaceConfiguration || null);
|
||||
|
||||
return { ...state, marketplaceConfiguration }
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
@ -1,26 +1,16 @@
|
||||
import { NitroToolbarAnimateIconEvent, TextureUtils, ToolbarIconEnum } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useRef, useState } from 'react';
|
||||
import { FC, useCallback, useRef } from 'react';
|
||||
import { GetRoomEngine } from '../../../../api';
|
||||
import { CatalogEvent, CatalogSelectProductEvent } from '../../../../events';
|
||||
import { CatalogWidgetEvent } from '../../../../events/catalog/CatalogWidgetEvent';
|
||||
import { CatalogPurchasedEvent } from '../../../../events';
|
||||
import { useUiEvent } from '../../../../hooks';
|
||||
import { RoomPreviewerView } from '../../../../views/shared/room-previewer/RoomPreviewerView';
|
||||
import { RoomPreviewerViewProps } from '../../../../views/shared/room-previewer/RoomPreviewerView.types';
|
||||
import { IPurchasableOffer } from '../../common/IPurchasableOffer';
|
||||
|
||||
export const CatalogRoomPreviewerView: FC<RoomPreviewerViewProps> = props =>
|
||||
{
|
||||
const { roomPreviewer = null } = props;
|
||||
const [ offer, setOffer ] = useState<IPurchasableOffer>(null);
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onCatalogSelectProductEvent = useCallback((event: CatalogSelectProductEvent) =>
|
||||
{
|
||||
setOffer(event.offer);
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SELECT_PRODUCT, onCatalogSelectProductEvent)
|
||||
|
||||
const animatePurchase = useCallback(() =>
|
||||
{
|
||||
if(!elementRef) return;
|
||||
@ -45,17 +35,12 @@ export const CatalogRoomPreviewerView: FC<RoomPreviewerViewProps> = props =>
|
||||
GetRoomEngine().events.dispatchEvent(event);
|
||||
}, [ roomPreviewer ]);
|
||||
|
||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||
const onCatalogPurchasedEvent = useCallback((event: CatalogPurchasedEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case CatalogEvent.PURCHASE_SUCCESS:
|
||||
animatePurchase();
|
||||
return;
|
||||
}
|
||||
}, [ animatePurchase ]);
|
||||
|
||||
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogPurchasedEvent);
|
||||
|
||||
return (
|
||||
<div ref={ elementRef }>
|
||||
|
@ -9,7 +9,7 @@ import { Column } from '../../../../common/Column';
|
||||
import { Flex } from '../../../../common/Flex';
|
||||
import { FormGroup } from '../../../../common/FormGroup';
|
||||
import { Text } from '../../../../common/Text';
|
||||
import { CatalogEvent } from '../../../../events';
|
||||
import { CatalogEvent, CatalogPurchasedEvent } from '../../../../events';
|
||||
import { CatalogInitGiftEvent } from '../../../../events/catalog/CatalogInitGiftEvent';
|
||||
import { BatchUpdates, SendMessageHook, useUiEvent } from '../../../../hooks';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutGiftCardView } from '../../../../layout';
|
||||
@ -32,8 +32,8 @@ export const CatalogGiftView: FC<{}> = props =>
|
||||
const [ maxBoxIndex, setMaxBoxIndex ] = useState<number>(0);
|
||||
const [ maxRibbonIndex, setMaxRibbonIndex ] = useState<number>(0);
|
||||
const [ receiverNotFound, setReceiverNotFound ] = useState<boolean>(false);
|
||||
const { catalogState = null } = useCatalogContext();
|
||||
const { giftConfiguration = null } = catalogState;
|
||||
const { catalogOptions = null } = useCatalogContext();
|
||||
const { giftConfiguration = null } = catalogOptions;
|
||||
|
||||
const close = useCallback(() =>
|
||||
{
|
||||
@ -56,7 +56,7 @@ export const CatalogGiftView: FC<{}> = props =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case CatalogEvent.PURCHASE_SUCCESS:
|
||||
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
|
||||
close();
|
||||
return;
|
||||
case CatalogEvent.INIT_GIFT:
|
||||
@ -77,7 +77,7 @@ export const CatalogGiftView: FC<{}> = props =>
|
||||
}
|
||||
}, [ close ]);
|
||||
|
||||
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.INIT_GIFT, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.GIFT_RECEIVER_NOT_FOUND, onCatalogEvent);
|
||||
|
||||
|
@ -19,7 +19,7 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutGridItem column={ false } itemActive={ node.isActive } onClick={ event => activateNode(node) }>
|
||||
<LayoutGridItem gap={ 1 } column={ false } itemActive={ node.isActive } onClick={ event => activateNode(node) }>
|
||||
<CatalogIconView icon={ node.iconId } />
|
||||
<Text grow truncate>{ node.localization }</Text>
|
||||
{ node.isBranch &&
|
||||
|
@ -1,121 +0,0 @@
|
||||
import { IObjectData, RoomPreviewer, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect } from 'react';
|
||||
import { GetAvatarRenderManager, GetSessionDataManager } from '../../../../api';
|
||||
import { CatalogPageReadyEvent, SetRoomPreviewerStuffDataEvent } from '../../../../events';
|
||||
import { dispatchUiEvent, useUiEvent } from '../../../../hooks';
|
||||
import { FurniCategory } from '../../common/FurniCategory';
|
||||
import { ICatalogPage } from '../../common/ICatalogPage';
|
||||
import { IPurchasableOffer } from '../../common/IPurchasableOffer';
|
||||
import { ProductTypeEnum } from '../../common/ProductTypeEnum';
|
||||
import { useCatalogContext } from '../../context/CatalogContext';
|
||||
import { GetCatalogLayout } from './layout/GetCatalogLayout';
|
||||
|
||||
export interface CatalogPageViewProps
|
||||
{
|
||||
page: ICatalogPage;
|
||||
roomPreviewer: RoomPreviewer;
|
||||
}
|
||||
|
||||
export const CatalogPageView: FC<CatalogPageViewProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
const updatePreviewerForOffer = useCallback((offer: IPurchasableOffer, stuffData: IObjectData = null) =>
|
||||
{
|
||||
if(!offer || !roomPreviewer) return;
|
||||
|
||||
const product = offer.product;
|
||||
|
||||
if(!product && !product.furnitureData && (product.productType !== ProductTypeEnum.ROBOT)) return;
|
||||
|
||||
switch(product.productType.toLowerCase())
|
||||
{
|
||||
case ProductTypeEnum.ROBOT: {
|
||||
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
||||
const figure = GetAvatarRenderManager().getFigureStringWithFigureIds(product.extraParam, 'm', []);
|
||||
|
||||
roomPreviewer.addAvatarIntoRoom(figure, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
case ProductTypeEnum.FLOOR: {
|
||||
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
||||
|
||||
if(product.furnitureData.specialType === FurniCategory.FIGURE_PURCHASABLE_SET)
|
||||
{
|
||||
const setIds: number[] = [];
|
||||
const sets = product.furnitureData.customParams.split(',');
|
||||
|
||||
for(const set of sets)
|
||||
{
|
||||
const setId = parseInt(set);
|
||||
|
||||
if(GetAvatarRenderManager().isValidFigureSetForGender(setId, GetSessionDataManager().gender)) setIds.push(setId);
|
||||
}
|
||||
|
||||
const figure = GetAvatarRenderManager().getFigureStringWithFigureIds(GetSessionDataManager().figure, GetSessionDataManager().gender, setIds);
|
||||
|
||||
roomPreviewer.addAvatarIntoRoom(figure, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
roomPreviewer.addFurnitureIntoRoom(product.productClassId, new Vector3d(90), stuffData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ProductTypeEnum.WALL: {
|
||||
switch(product.furnitureData.className)
|
||||
{
|
||||
case 'floor':
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateObjectRoom(product.extraParam);
|
||||
break;
|
||||
case 'wallpaper':
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateObjectRoom(null, product.extraParam);
|
||||
break;
|
||||
case 'landscape':
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateObjectRoom(null, null, product.extraParam);
|
||||
break;
|
||||
default:
|
||||
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
||||
roomPreviewer.addWallItemIntoRoom(product.productClassId, new Vector3d(90), product.extraParam);
|
||||
return;
|
||||
}
|
||||
|
||||
const windowData = GetSessionDataManager().getWallItemDataByName('noob_window_double');
|
||||
|
||||
if(windowData) roomPreviewer.addWallItemIntoRoom(windowData.id, new Vector3d(90, 0, 0), windowData.customParams);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [ roomPreviewer ]);
|
||||
|
||||
const onSetRoomPreviewerStuffDataEvent = useCallback((event: SetRoomPreviewerStuffDataEvent) =>
|
||||
{
|
||||
if(roomPreviewer) roomPreviewer.reset(false);
|
||||
|
||||
updatePreviewerForOffer(event.offer, event.stuffData);
|
||||
}, [ roomPreviewer, updatePreviewerForOffer ]);
|
||||
|
||||
useUiEvent(SetRoomPreviewerStuffDataEvent.UPDATE_STUFF_DATA, onSetRoomPreviewerStuffDataEvent);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
updatePreviewerForOffer(currentOffer);
|
||||
}, [ currentOffer, updatePreviewerForOffer ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
dispatchUiEvent(new CatalogPageReadyEvent());
|
||||
}, [ page ]);
|
||||
|
||||
if(!page) return null;
|
||||
|
||||
return GetCatalogLayout(page, roomPreviewer);
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
import { RoomPreviewer } from '@nitrots/nitro-renderer';
|
||||
import { ICatalogPage } from '../../../common/ICatalogPage';
|
||||
|
||||
export interface CatalogLayoutProps
|
||||
{
|
||||
page: ICatalogPage;
|
||||
roomPreviewer: RoomPreviewer;
|
||||
}
|
||||
|
@ -1,78 +1,58 @@
|
||||
import { StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Base } from '../../../../../common/Base';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { LayoutGridItem } from '../../../../../common/layout/LayoutGridItem';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { InventoryBadgesUpdatedEvent, SetRoomPreviewerStuffDataEvent } from '../../../../../events';
|
||||
import { InventoryBadgesRequestEvent } from '../../../../../events/inventory/InventoryBadgesRequestEvent';
|
||||
import { dispatchUiEvent, useUiEvent } from '../../../../../hooks';
|
||||
import { BadgeImageView } from '../../../../../views/shared/badge-image/BadgeImageView';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogProductPreviewView } from '../offers/CatalogPageOfferPreviewView';
|
||||
import { CatalogPageOffersView } from '../offers/CatalogPageOffersView';
|
||||
import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView';
|
||||
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
|
||||
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
|
||||
import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget';
|
||||
import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const [ badges, setBadges ] = useState<string[]>([]);
|
||||
const [ currentBadge, setCurrentBadge ] = useState<string>(null);
|
||||
const { page = null } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
const onInventoryBadgesUpdatedEvent = useCallback((event: InventoryBadgesUpdatedEvent) =>
|
||||
{
|
||||
setBadges(event.badges);
|
||||
}, []);
|
||||
|
||||
useUiEvent(InventoryBadgesUpdatedEvent.BADGES_UPDATED, onInventoryBadgesUpdatedEvent);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
dispatchUiEvent(new InventoryBadgesRequestEvent(InventoryBadgesRequestEvent.REQUEST_BADGES));
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentBadge || !currentOffer) return;
|
||||
|
||||
const productData = [];
|
||||
|
||||
productData.push('0');
|
||||
productData.push(currentBadge);
|
||||
productData.push('');
|
||||
productData.push('');
|
||||
productData.push('');
|
||||
|
||||
const stringDataType = new StringDataType();
|
||||
stringDataType.setValue(productData);
|
||||
|
||||
dispatchUiEvent(new SetRoomPreviewerStuffDataEvent(currentOffer, stringDataType));
|
||||
}, [ currentBadge, currentOffer, roomPreviewer ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CatalogFirstProductSelectorWidgetView />
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<CatalogPageOffersView shrink offers={ page.offers } />
|
||||
<CatalogItemGridWidgetView shrink />
|
||||
<Column gap={ 1 } overflow="hidden">
|
||||
<Text truncate shrink fontWeight="bold">{ LocalizeText('catalog_selectbadge') }</Text>
|
||||
<Grid grow columnCount={ 5 } overflow="auto">
|
||||
{ badges && (badges.length > 0) && badges.map(code =>
|
||||
{
|
||||
return (
|
||||
<LayoutGridItem key={ code } itemActive={ (currentBadge === code) } onMouseDown={ event => setCurrentBadge(code) }>
|
||||
<BadgeImageView badgeCode={ code } />
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</Grid>
|
||||
<CatalogBadgeSelectorWidgetView />
|
||||
</Column>
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ !!currentOffer &&
|
||||
<CatalogProductPreviewView offer={ currentOffer } roomPreviewer={ roomPreviewer } extra={ currentBadge } disabled={ !currentBadge } /> }
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Base position="relative" overflow="hidden">
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogLimitedItemWidgetView fullWidth position="absolute" className="top-1" />
|
||||
</Base>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text grow truncate>{ currentOffer.localizationName }</Text>
|
||||
<Flex justifyContent="end">
|
||||
<CatalogTotalPriceWidget alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,22 @@
|
||||
import { FC } from 'react';
|
||||
import { Base } from '../../../../../common/Base';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogPageDetailsView } from '../../page-details/CatalogPageDetailsView';
|
||||
import { CatalogProductPreviewView } from '../offers/CatalogPageOfferPreviewView';
|
||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
|
||||
import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogSpinnerWidgetView } from '../widgets/CatalogSpinnerWidgetView';
|
||||
import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget';
|
||||
import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const { page = null } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
return (
|
||||
@ -17,11 +24,30 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<CatalogItemGridWidgetView />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<CatalogPageDetailsView page={ page } /> }
|
||||
{ !!currentOffer &&
|
||||
<CatalogProductPreviewView offer={ currentOffer } roomPreviewer={ roomPreviewer } /> }
|
||||
<>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Base position="relative" overflow="hidden">
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogLimitedItemWidgetView fullWidth position="absolute" className="top-1" />
|
||||
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-1 end-1" />
|
||||
</Base>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text grow truncate>{ currentOffer.localizationName }</Text>
|
||||
<Flex justifyContent="between">
|
||||
<Column gap={ 1 }>
|
||||
<CatalogSpinnerWidgetView />
|
||||
</Column>
|
||||
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -1,67 +1,51 @@
|
||||
import { CatalogGroupsComposer, StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { Base } from '../../../../../common/Base';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { SetRoomPreviewerStuffDataEvent } from '../../../../../events';
|
||||
import { dispatchUiEvent } from '../../../../../hooks';
|
||||
import { SendMessageHook } from '../../../../../hooks/messages';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogSelectGroupView } from '../../select-group/CatalogSelectGroupView';
|
||||
import { CatalogProductPreviewView } from '../offers/CatalogPageOfferPreviewView';
|
||||
import { CatalogPageOffersView } from '../offers/CatalogPageOffersView';
|
||||
import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView';
|
||||
import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView';
|
||||
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget';
|
||||
import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
|
||||
const { currentOffer = null, catalogState = null } = useCatalogContext();
|
||||
const { groups = null } = catalogState;
|
||||
|
||||
const selectedGroup = useMemo(() =>
|
||||
{
|
||||
if(!groups || !groups.length) return;
|
||||
|
||||
return groups[selectedGroupIndex];
|
||||
}, [ groups, selectedGroupIndex ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!page) return;
|
||||
|
||||
SendMessageHook(new CatalogGroupsComposer());
|
||||
}, [ page ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentOffer || !groups[selectedGroupIndex]) return;
|
||||
|
||||
const productData = [];
|
||||
|
||||
productData.push('0');
|
||||
productData.push(groups[selectedGroupIndex].groupId);
|
||||
productData.push(groups[selectedGroupIndex].badgeCode);
|
||||
productData.push(groups[selectedGroupIndex].colorA);
|
||||
productData.push(groups[selectedGroupIndex].colorB);
|
||||
|
||||
const stringDataType = new StringDataType();
|
||||
stringDataType.setValue(productData);
|
||||
|
||||
dispatchUiEvent(new SetRoomPreviewerStuffDataEvent(currentOffer, stringDataType));
|
||||
}, [ groups, currentOffer, selectedGroupIndex ]);
|
||||
|
||||
if(!groups) return null;
|
||||
const { page = null } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<CatalogPageOffersView offers={ page.offers } />
|
||||
<CatalogItemGridWidgetView />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ !!currentOffer &&
|
||||
<CatalogProductPreviewView offer={ currentOffer } roomPreviewer={ roomPreviewer } badgeCode={ ((selectedGroup && selectedGroup.badgeCode) || null) } extra={ groups[selectedGroupIndex] ? groups[selectedGroupIndex].groupId.toString() : '' } disabled={ !(!!groups[selectedGroupIndex]) }>
|
||||
<CatalogSelectGroupView selectedGroupIndex={ selectedGroupIndex } setSelectedGroupIndex={ setSelectedGroupIndex } />
|
||||
</CatalogProductPreviewView> }
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Base position="relative" overflow="hidden">
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogGuildBadgeWidgetView position="absolute" className="bottom-1 end-1" />
|
||||
</Base>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text truncate>{ currentOffer.localizationName }</Text>
|
||||
<Base grow>
|
||||
<CatalogGuildSelectorWidgetView />
|
||||
</Base>
|
||||
<Flex justifyContent="end">
|
||||
<CatalogTotalPriceWidget alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -5,7 +5,6 @@ import { Column } from '../../../../../common/Column';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { SendMessageHook } from '../../../../../hooks/messages';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogSelectGroupView } from '../../select-group/CatalogSelectGroupView';
|
||||
import { CatalogProductPreviewView } from '../offers/CatalogPageOfferPreviewView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
@ -13,8 +12,8 @@ export const CatalogLayouGuildForumView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null } = props;
|
||||
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
|
||||
const { currentOffer = null, setCurrentOffer = null, catalogState = null, dispatchCatalogState = null } = useCatalogContext();
|
||||
const { groups = null } = catalogState;
|
||||
const { currentOffer = null, setCurrentOffer = null, catalogOptions = null } = useCatalogContext();
|
||||
const { groups = null } = catalogOptions;
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -31,7 +30,7 @@ export const CatalogLayouGuildForumView: FC<CatalogLayoutProps> = props =>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ !!currentOffer &&
|
||||
<CatalogProductPreviewView offer={ currentOffer } roomPreviewer={ null } extra={ groups[selectedGroupIndex] ? groups[selectedGroupIndex].groupId.toString() : '' } disabled={ !(!!groups[selectedGroupIndex]) }>
|
||||
<CatalogSelectGroupView selectedGroupIndex={ selectedGroupIndex } setSelectedGroupIndex={ setSelectedGroupIndex } />
|
||||
{/* <CatalogSelectGroupView selectedGroupIndex={ selectedGroupIndex } setSelectedGroupIndex={ setSelectedGroupIndex } /> */}
|
||||
</CatalogProductPreviewView> }
|
||||
</Column>
|
||||
</Grid>
|
||||
|
@ -1,27 +1,43 @@
|
||||
import { FC } from 'react';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogPageDetailsView } from '../../page-details/CatalogPageDetailsView';
|
||||
import { CatalogPurchaseView } from '../purchase/CatalogPurchaseView';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||
import { CatalogBundleGridWidgetView } from '../widgets/CatalogBundleGridWidgetView';
|
||||
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogSimplePriceWidgetView } from '../widgets/CatalogSimplePriceWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
export const CatalogLayoutRoomBundleView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
const { page = null } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CatalogFirstProductSelectorWidgetView />
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<Grid grow overflow="auto">
|
||||
{/* { currentOffer && currentOffer.products && (activeOffer.products.length > 0) && activeOffer.products.map((product, index) => <CatalogProductView key={ index } itemActive={ false } product={ product } />)} */}
|
||||
</Grid>
|
||||
{ !!page.localization.getText(2) &&
|
||||
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
|
||||
<Column grow overflow="hidden" className="bg-muted p-2 rounded">
|
||||
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
|
||||
</Column>
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden" gap={ 1 }>
|
||||
{ !!page.localization.getText(1) &&
|
||||
<Text center small overflow="auto">{ page.localization.getText(1) }</Text> }
|
||||
<Column grow position="relative" overflow="hidden" gap={ 0 }>
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<img alt="" className="flex-grow-1" src={ page.localization.getImage(1) } /> }
|
||||
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-0 start-0" />
|
||||
<CatalogSimplePriceWidgetView position="absolute" className="bottom-0 end-0" />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
<CatalogPageDetailsView page={ page } />
|
||||
{ currentOffer && <CatalogPurchaseView offer={ currentOffer } pageId={ page.pageId } /> }
|
||||
</Column>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { FC } from 'react';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||
import { CatalogBundleGridWidgetView } from '../widgets/CatalogBundleGridWidgetView';
|
||||
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogSimplePriceWidgetView } from '../widgets/CatalogSimplePriceWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
@ -13,27 +13,27 @@ export const CatalogLayoutSingleBundleView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null } = props;
|
||||
|
||||
const imageUrl = page.localization.getImage(1);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CatalogFirstProductSelectorWidgetView />
|
||||
<Grid>
|
||||
<Column justifyContent="between" size={ 7 } overflow="hidden">
|
||||
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } />
|
||||
<Column fit overflow="hidden" className="bg-muted p-2 rounded">
|
||||
<CatalogBundleGridWidgetView className="nitro-catalog-layout-bundle-grid" fullWidth />
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
{ !!page.localization.getText(2) &&
|
||||
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
|
||||
<Column grow overflow="hidden" className="bg-muted p-2 rounded">
|
||||
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
|
||||
</Column>
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden" gap={ 1 }>
|
||||
<Text center overflow="auto">{ page.localization.getText(1) }</Text>
|
||||
<Flex fullHeight center position="relative" overflow="hidden">
|
||||
{ imageUrl && <img className="" alt="" src={ imageUrl } /> }
|
||||
<CatalogAddOnBadgeWidgetView position="absolute" className="bottom-0 start-0" />
|
||||
</Flex>
|
||||
<Column gap={ 1 } position="relative">
|
||||
<Flex center>
|
||||
<CatalogSimplePriceWidgetView />
|
||||
</Flex>
|
||||
{ !!page.localization.getText(1) &&
|
||||
<Text center small overflow="auto">{ page.localization.getText(1) }</Text> }
|
||||
<Column grow position="relative" overflow="hidden" gap={ 0 }>
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<img alt="" className="flex-grow-1" src={ page.localization.getImage(1) } /> }
|
||||
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-0 start-0" />
|
||||
<CatalogSimplePriceWidgetView position="absolute" className="bottom-0 end-0" />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</Column>
|
||||
|
@ -1,73 +1,51 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Button } from '../../../../../common/Button';
|
||||
import { ButtonGroup } from '../../../../../common/ButtonGroup';
|
||||
import { NitroPoint } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { Base } from '../../../../../common/Base';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { BatchUpdates } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogProductPreviewView } from '../offers/CatalogPageOfferPreviewView';
|
||||
import { CatalogPageOffersView } from '../offers/CatalogPageOffersView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogSpacesWidgetView } from '../widgets/CatalogSpacesWidgetView';
|
||||
import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget';
|
||||
import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const [ groups, setGroups ] = useState<IPurchasableOffer[][]>([]);
|
||||
const [ activeGroupIndex, setActiveGroupIndex ] = useState(-1);
|
||||
const { currentOffer = null, catalogState } = useCatalogContext();
|
||||
|
||||
const groupNames = [ 'floors', 'walls', 'views' ];
|
||||
const { page = null } = props;
|
||||
const { currentOffer = null, roomPreviewer = null } = useCatalogContext();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!page) return;
|
||||
|
||||
const groupedOffers: IPurchasableOffer[][] = [ [], [], [] ];
|
||||
|
||||
for(const offer of page.offers)
|
||||
{
|
||||
const product = offer.product
|
||||
|
||||
if(!product) continue;
|
||||
|
||||
if(!product.furnitureData) continue;
|
||||
|
||||
const className = product.furnitureData.className;
|
||||
|
||||
switch(className)
|
||||
{
|
||||
case 'floor':
|
||||
groupedOffers[0].push(offer);
|
||||
break;
|
||||
case 'wallpaper':
|
||||
groupedOffers[1].push(offer);
|
||||
break;
|
||||
case 'landscape':
|
||||
groupedOffers[2].push(offer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setGroups(groupedOffers);
|
||||
setActiveGroupIndex(0);
|
||||
});
|
||||
}, [ page ]);
|
||||
roomPreviewer.updatePreviewObjectBoundingRectangle(new NitroPoint());
|
||||
}, [ roomPreviewer ]);
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<ButtonGroup>
|
||||
{ groupNames.map((name, index) => <Button key={ index } active={ (activeGroupIndex === index) } onClick={ event => setActiveGroupIndex(index) }>{ LocalizeText(`catalog.spaces.tab.${ name }`) }</Button>)}
|
||||
</ButtonGroup>
|
||||
<CatalogPageOffersView offers={ groups[activeGroupIndex] } />
|
||||
<CatalogSpacesWidgetView />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ !!currentOffer &&
|
||||
<CatalogProductPreviewView offer={ currentOffer } roomPreviewer={ roomPreviewer } /> }
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Base position="relative" overflow="hidden">
|
||||
<CatalogViewProductWidgetView />
|
||||
</Base>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text grow truncate>{ currentOffer.localizationName }</Text>
|
||||
<Flex justifyContent="end">
|
||||
<CatalogTotalPriceWidget alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -1,26 +1,56 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogProductPreviewView } from '../offers/CatalogPageOfferPreviewView';
|
||||
import { CatalogPageOffersView } from '../offers/CatalogPageOffersView';
|
||||
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget';
|
||||
import { CatalogViewProductWidgetView } from '../widgets/CatalogViewProductWidgetView';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
|
||||
export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const { page = null } = props;
|
||||
const [ trophyText, setTrophyText ] = useState<string>('');
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
const { currentOffer = null, setPurchaseOptions = null } = useCatalogContext();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
setPurchaseOptions(prevValue =>
|
||||
{
|
||||
const extraData = trophyText;
|
||||
|
||||
return { ...prevValue, extraData };
|
||||
});
|
||||
}, [ currentOffer, trophyText, setPurchaseOptions ]);
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<CatalogPageOffersView offers={ page.offers } />
|
||||
<CatalogItemGridWidgetView />
|
||||
<textarea className="flex-grow-1 form-control w-100" defaultValue={ trophyText || '' } onChange={ event => setTrophyText(event.target.value) } />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ !!currentOffer &&
|
||||
<CatalogProductPreviewView offer={ currentOffer } roomPreviewer={ roomPreviewer } extra={ trophyText } /> }
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<CatalogViewProductWidgetView />
|
||||
<Column grow gap={ 1 }>
|
||||
<Text grow truncate>{ currentOffer.localizationName }</Text>
|
||||
<Flex justifyContent="end">
|
||||
<CatalogTotalPriceWidget alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import { Flex } from '../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { LayoutGridItem } from '../../../../../common/layout/LayoutGridItem';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { CatalogPurchasedEvent, CatalogPurchaseFailureEvent } from '../../../../../events';
|
||||
import { CatalogEvent } from '../../../../../events/catalog/CatalogEvent';
|
||||
import { useUiEvent } from '../../../../../hooks';
|
||||
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
|
||||
@ -22,24 +23,24 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
|
||||
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
|
||||
const { currentPage = null, catalogState = null } = useCatalogContext();
|
||||
const { clubOffers = null, subscriptionInfo = null } = catalogState;
|
||||
const { currentPage = null, catalogOptions = null } = useCatalogContext();
|
||||
const { clubOffers = null, subscriptionInfo = null } = catalogOptions;
|
||||
|
||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case CatalogEvent.PURCHASE_SUCCESS:
|
||||
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
return;
|
||||
case CatalogEvent.PURCHASE_FAILED:
|
||||
case CatalogPurchaseFailureEvent.PURCHASE_FAILED:
|
||||
setPurchaseState(CatalogPurchaseState.FAILED);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.PURCHASE_FAILED, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchaseFailureEvent.PURCHASE_FAILED, onCatalogEvent);
|
||||
|
||||
const getOfferText = useCallback((offer: ClubOfferData) =>
|
||||
{
|
||||
@ -145,7 +146,7 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column fullHeight size={ 7 } overflow="hidden">
|
||||
<Column fullHeight size={ 7 } overflow="hidden" justifyContent="between">
|
||||
<Grid grow columnCount={ 1 } className="nitro-catalog-layout-vip-buy-grid" overflow="auto">
|
||||
{ clubOffers && (clubOffers.length > 0) && clubOffers.map((offer, index) =>
|
||||
{
|
||||
@ -169,9 +170,9 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
|
||||
</Column>
|
||||
</LayoutGridItem>
|
||||
);
|
||||
})}
|
||||
<Text dangerouslySetInnerHTML={{ __html: LocalizeText('catalog.vip.buy.hccenter') }}></Text>
|
||||
}) }
|
||||
</Grid>
|
||||
<Text center dangerouslySetInnerHTML={{ __html: LocalizeText('catalog.vip.buy.hccenter') }}></Text>
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
<Column fullHeight center overflow="hidden">
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { RoomPreviewer } from '@nitrots/nitro-renderer';
|
||||
import { ICatalogPage } from '../../../common/ICatalogPage';
|
||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
import { CatalogLayoutBadgeDisplayView } from './CatalogLayoutBadgeDisplayView';
|
||||
@ -20,9 +19,11 @@ import { CatalogLayoutMarketplacePublicItemsView } from './marketplace/CatalogLa
|
||||
import { CatalogLayoutPetView } from './pets/CatalogLayoutPetView';
|
||||
import { CatalogLayoutVipGiftsView } from './vip-gifts/CatalogLayoutVipGiftsView';
|
||||
|
||||
export const GetCatalogLayout = (page: ICatalogPage, roomPreviewer: RoomPreviewer) =>
|
||||
export const GetCatalogLayout = (page: ICatalogPage) =>
|
||||
{
|
||||
const layoutProps: CatalogLayoutProps = { page, roomPreviewer };
|
||||
if(!page) return null;
|
||||
|
||||
const layoutProps: CatalogLayoutProps = { page };
|
||||
|
||||
switch(page.layoutCode)
|
||||
{
|
||||
@ -44,8 +45,6 @@ export const GetCatalogLayout = (page: ICatalogPage, roomPreviewer: RoomPreviewe
|
||||
return <CatalogLayouGuildForumView { ...layoutProps } />;
|
||||
case 'guild_custom_furni':
|
||||
return <CatalogLayouGuildCustomFurniView { ...layoutProps } />;
|
||||
case 'search_results':
|
||||
return null;
|
||||
case 'club_gifts':
|
||||
return <CatalogLayoutVipGiftsView { ...layoutProps } />;
|
||||
case 'marketplace_own_items':
|
||||
@ -59,11 +58,11 @@ export const GetCatalogLayout = (page: ICatalogPage, roomPreviewer: RoomPreviewe
|
||||
case 'spaces_new':
|
||||
return <CatalogLayoutSpacesView { ...layoutProps } />;
|
||||
case 'trophies':
|
||||
return <CatalogLayoutTrophiesView roomPreviewer={roomPreviewer} page={ page } />;
|
||||
return <CatalogLayoutTrophiesView page={ page } />;
|
||||
case 'info_loyalty':
|
||||
return <CatalogLayoutInfoLoyaltyView { ...layoutProps } />;
|
||||
case 'badge_display':
|
||||
return <CatalogLayoutBadgeDisplayView roomPreviewer={roomPreviewer} page={ page } />;
|
||||
return <CatalogLayoutBadgeDisplayView page={ page } />;
|
||||
//case 'default_3x3_color_grouping':
|
||||
//return <CatalogLayoutColorGroupingView { ...layoutProps } />;
|
||||
case 'bots':
|
||||
|
@ -18,7 +18,8 @@ export const MarketplacePostOfferView : FC<{}> = props =>
|
||||
{
|
||||
const [ item, setItem ] = useState<FurnitureItem>(null);
|
||||
const [ askingPrice, setAskingPrice ] = useState(0);
|
||||
const { catalogState = null, dispatchCatalogState = null } = useCatalogContext();
|
||||
const { catalogOptions = null } = useCatalogContext();
|
||||
const { marketplaceConfiguration = null } = catalogOptions;
|
||||
|
||||
const close = useCallback(() =>
|
||||
{
|
||||
@ -105,24 +106,24 @@ export const MarketplacePostOfferView : FC<{}> = props =>
|
||||
</Column>
|
||||
<Column overflow="auto">
|
||||
<Text italics>
|
||||
{ LocalizeText('inventory.marketplace.make_offer.expiration_info', ['time'], [catalogState.marketplaceConfiguration.offerTime.toString()]) }
|
||||
{ LocalizeText('inventory.marketplace.make_offer.expiration_info', ['time'], [marketplaceConfiguration.offerTime.toString()]) }
|
||||
</Text>
|
||||
<div className="input-group has-validation">
|
||||
<input className="form-control form-control-sm" type="number" min={ 0 } value={ askingPrice } onChange={ event => setAskingPrice(event.target.valueAsNumber) } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } />
|
||||
{ ((askingPrice < catalogState.marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) &&
|
||||
{ ((askingPrice < marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) &&
|
||||
<Base className="invalid-feedback d-block">
|
||||
{ LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ catalogState.marketplaceConfiguration.minimumPrice.toString() ]) }
|
||||
{ LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ marketplaceConfiguration.minimumPrice.toString() ]) }
|
||||
</Base> }
|
||||
{ ((askingPrice > catalogState.marketplaceConfiguration.maximumPrice) && !isNaN(askingPrice)) &&
|
||||
{ ((askingPrice > marketplaceConfiguration.maximumPrice) && !isNaN(askingPrice)) &&
|
||||
<Base className="invalid-feedback d-block">
|
||||
{ LocalizeText('inventory.marketplace.make_offer.max_price', [ 'maxprice' ], [ catalogState.marketplaceConfiguration.maximumPrice.toString() ]) }
|
||||
{ LocalizeText('inventory.marketplace.make_offer.max_price', [ 'maxprice' ], [ marketplaceConfiguration.maximumPrice.toString() ]) }
|
||||
</Base> }
|
||||
{ (!((askingPrice < catalogState.marketplaceConfiguration.minimumPrice) || (askingPrice > catalogState.marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))) &&
|
||||
{ (!((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))) &&
|
||||
<Base className="invalid-feedback d-block">
|
||||
{ LocalizeText('inventory.marketplace.make_offer.final_price', [ 'commission', 'finalprice' ], [ catalogState.marketplaceConfiguration.commission.toString(), (askingPrice + catalogState.marketplaceConfiguration.commission).toString() ]) }
|
||||
{ LocalizeText('inventory.marketplace.make_offer.final_price', [ 'commission', 'finalprice' ], [ marketplaceConfiguration.commission.toString(), (askingPrice + marketplaceConfiguration.commission).toString() ]) }
|
||||
</Base> }
|
||||
</div>
|
||||
<Button size="sm" disabled={ ((askingPrice < catalogState.marketplaceConfiguration.minimumPrice) || (askingPrice > catalogState.marketplaceConfiguration.maximumPrice) || isNaN(askingPrice)) } onClick={ postItem }>
|
||||
<Button size="sm" disabled={ ((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice)) } onClick={ postItem }>
|
||||
{ LocalizeText('inventory.marketplace.make_offer.post') }
|
||||
</Button>
|
||||
</Column>
|
||||
|
@ -4,14 +4,13 @@ import { LocalizeText } from '../../../../../../api';
|
||||
import { Column } from '../../../../../../common/Column';
|
||||
import { Flex } from '../../../../../../common/Flex';
|
||||
import { Text } from '../../../../../../common/Text';
|
||||
import { CatalogEvent } from '../../../../../../events';
|
||||
import { CatalogPurchasedEvent } from '../../../../../../events';
|
||||
import { useUiEvent } from '../../../../../../hooks/events/ui/ui-event';
|
||||
import { SendMessageHook } from '../../../../../../hooks/messages/message-event';
|
||||
import { CurrencyIcon } from '../../../../../../views/shared/currency-icon/CurrencyIcon';
|
||||
import { IPurchasableOffer } from '../../../../common/IPurchasableOffer';
|
||||
import { Offer } from '../../../../common/Offer';
|
||||
import { CatalogPurchaseButtonView } from '../../purchase/CatalogPurchaseButtonView';
|
||||
import { CatalogPurchaseGiftButtonView } from '../../purchase/CatalogPurchaseGiftButtonView';
|
||||
import { CatalogPurchaseWidgetView } from '../../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogPetNameApprovalView } from './CatalogPetNameApprovalView';
|
||||
|
||||
export interface CatalogLayoutPetPurchaseViewProps
|
||||
@ -27,17 +26,12 @@ export const CatalogLayoutPetPurchaseView: FC<CatalogLayoutPetPurchaseViewProps>
|
||||
const [ petNameValue, setPetNameValue ] = useState('');
|
||||
const [ nameApproved, setNameApproved ] = useState(false);
|
||||
|
||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||
const onCatalogPurchasedEvent = useCallback((event: CatalogPurchasedEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case CatalogEvent.PURCHASE_SUCCESS:
|
||||
setNameApproved(false);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogPurchasedEvent);
|
||||
|
||||
const beforePurchase = useCallback(() =>
|
||||
{
|
||||
@ -69,9 +63,10 @@ export const CatalogLayoutPetPurchaseView: FC<CatalogLayoutPetPurchaseViewProps>
|
||||
</Column>
|
||||
</Flex>
|
||||
<Column gap={ 1 }>
|
||||
<CatalogPurchaseButtonView offer={ offer } pageId={ pageId } extra={ extraData } quantity={ 1 } isPurchaseAllowed={ nameApproved } beforePurchase={ beforePurchase } />
|
||||
<CatalogPurchaseWidgetView />
|
||||
{/* <CatalogPurchaseButtonView offer={ offer } pageId={ pageId } extra={ extraData } quantity={ 1 } isPurchaseAllowed={ nameApproved } beforePurchase={ beforePurchase } />
|
||||
{ offer.giftable &&
|
||||
<CatalogPurchaseGiftButtonView offer={ offer } pageId={ pageId } extra={ extraData } disabled={ nameApproved } /> }
|
||||
<CatalogPurchaseGiftButtonView offer={ offer } pageId={ pageId } extra={ extraData } disabled={ nameApproved } /> } */}
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
|
@ -1,34 +1,71 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { ColorConverter, GetSellablePetPalettesComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../../../api';
|
||||
import { Base } from '../../../../../../common/Base';
|
||||
import { Button } from '../../../../../../common/Button';
|
||||
import { Column } from '../../../../../../common/Column';
|
||||
import { Flex } from '../../../../../../common/Flex';
|
||||
import { Grid } from '../../../../../../common/Grid';
|
||||
import { LayoutGridItem } from '../../../../../../common/layout/LayoutGridItem';
|
||||
import { Text } from '../../../../../../common/Text';
|
||||
import { BatchUpdates } from '../../../../../../hooks';
|
||||
import { CatalogNameResultEvent } from '../../../../../../events';
|
||||
import { CatalogWidgetEvent } from '../../../../../../events/catalog/CatalogWidgetEvent';
|
||||
import { BatchUpdates, useUiEvent } from '../../../../../../hooks';
|
||||
import { SendMessageHook } from '../../../../../../hooks/messages/message-event';
|
||||
import { PetImageView } from '../../../../../../views/shared/pet-image/PetImageView';
|
||||
import { GetPetAvailableColors, GetPetIndexFromLocalization } from '../../../../common/CatalogUtilities';
|
||||
import { useCatalogContext } from '../../../../context/CatalogContext';
|
||||
import { CatalogRoomPreviewerView } from '../../../catalog-room-previewer/CatalogRoomPreviewerView';
|
||||
import { CatalogPageDetailsView } from '../../../page-details/CatalogPageDetailsView';
|
||||
import { CatalogAddOnBadgeWidgetView } from '../../widgets/CatalogAddOnBadgeWidgetView';
|
||||
import { CatalogPurchaseWidgetView } from '../../widgets/CatalogPurchaseWidgetView';
|
||||
import { CatalogTotalPriceWidget } from '../../widgets/CatalogTotalPriceWidget';
|
||||
import { CatalogViewProductWidgetView } from '../../widgets/CatalogViewProductWidgetView';
|
||||
import { CatalogLayoutProps } from '../CatalogLayout.types';
|
||||
import { CatalogLayoutPetPurchaseView } from './CatalogLayoutPetPurchaseView';
|
||||
|
||||
export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null, roomPreviewer = null } = props;
|
||||
const { page = null } = props;
|
||||
const [ petIndex, setPetIndex ] = useState(-1);
|
||||
const [ sellablePalettes, setSellablePalettes ] = useState<SellablePetPaletteData[]>([]);
|
||||
const [ selectedPaletteIndex, setSelectedPaletteIndex ] = useState(-1);
|
||||
const [ sellableColors, setSellableColors ] = useState<number[][]>([]);
|
||||
const [ selectedColorIndex, setSelectedColorIndex ] = useState(-1);
|
||||
const [ colorsShowing, setColorsShowing ] = useState(false);
|
||||
const { currentOffer = null, setCurrentOffer = null, catalogState = null, dispatchCatalogState = null } = useCatalogContext();
|
||||
const { petPalettes = [] } = catalogState;
|
||||
const [ petName, setPetName ] = useState('');
|
||||
const [ approvalPending, setApprovalPending ] = useState(true);
|
||||
const [ approvalResult, setApprovalResult ] = useState(-1);
|
||||
const { currentOffer = null, setCurrentOffer = null, catalogOptions = null, roomPreviewer = null } = useCatalogContext();
|
||||
const { petPalettes = [] } = catalogOptions;
|
||||
|
||||
const validationErrorMessage = () =>
|
||||
{
|
||||
let key: string = '';
|
||||
|
||||
switch(approvalResult)
|
||||
{
|
||||
case 1:
|
||||
key = 'catalog.alert.petname.long';
|
||||
break;
|
||||
case 2:
|
||||
key = 'catalog.alert.petname.short';
|
||||
break;
|
||||
case 3:
|
||||
key = 'catalog.alert.petname.chars';
|
||||
break;
|
||||
case 4:
|
||||
key = 'catalog.alert.petname.bobba';
|
||||
break;
|
||||
}
|
||||
|
||||
return LocalizeText(key);
|
||||
}
|
||||
|
||||
const onCatalogNameResultEvent = useCallback((event: CatalogNameResultEvent) =>
|
||||
{
|
||||
setApprovalPending(false);
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.APPROVE_RESULT, onCatalogNameResultEvent)
|
||||
|
||||
const getColor = useMemo(() =>
|
||||
{
|
||||
@ -136,7 +173,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
if(!roomPreviewer) return;
|
||||
|
||||
roomPreviewer && roomPreviewer.reset(false);
|
||||
roomPreviewer.reset(false);
|
||||
|
||||
if((petIndex === -1) || !sellablePalettes.length || (selectedPaletteIndex === -1)) return;
|
||||
|
||||
@ -164,23 +201,33 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
||||
{ colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => <LayoutGridItem key={ index } itemActive={ (selectedColorIndex === index) } itemColor={ ColorConverter.int2rgb(colorSet[0]) } onClick={ event => setSelectedColorIndex(index) } />) }
|
||||
</Grid>
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ (petIndex === -1) &&
|
||||
<CatalogPageDetailsView page={ page } /> }
|
||||
{ (petIndex >= 0) &&
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
<Column overflow="hidden" position="relative" gap={ 0 }>
|
||||
{ roomPreviewer && <CatalogRoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } /> }
|
||||
{ (petIndex > -1 && petIndex <= 7) &&
|
||||
<Base position="absolute" className="start-1 bottom-1">
|
||||
<Button size="sm" active={ colorsShowing } onClick={ event => setColorsShowing(!colorsShowing) }>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Base position="relative" overflow="hidden">
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-1 end-1" />
|
||||
{ ((petIndex > -1) && (petIndex <= 7)) &&
|
||||
<Button position="absolute" className="bottom-1 start-1" onClick={ event => setColorsShowing(!colorsShowing) }>
|
||||
<FontAwesomeIcon icon="fill-drip" />
|
||||
</Button>
|
||||
</Base> }
|
||||
</Button> }
|
||||
</Base>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text truncate>{ petBreedName }</Text>
|
||||
<Column grow gap={ 1 }>
|
||||
<input type="text" className="form-control form-control-sm w-100" placeholder={ LocalizeText('widgets.petpackage.name.title') } value={ petName } onChange={ event => setPetName(event.target.value) } />
|
||||
{ (approvalResult > 0) &&
|
||||
<Base className="invalid-feedback">{ validationErrorMessage }</Base> }
|
||||
</Column>
|
||||
<Column grow>
|
||||
<Text grow truncate>{ petBreedName }</Text>
|
||||
<CatalogLayoutPetPurchaseView offer={ currentOffer } pageId={ page.pageId } extra={ petPurchaseString } />
|
||||
<Flex justifyContent="end">
|
||||
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
|
@ -6,62 +6,48 @@ import { Text } from '../../../../../../common/Text';
|
||||
import { SendMessageHook } from '../../../../../../hooks';
|
||||
import { NotificationUtilities } from '../../../../../../views/notification-center/common/NotificationUtilities';
|
||||
import { useCatalogContext } from '../../../../context/CatalogContext';
|
||||
import { CatalogActions } from '../../../../reducers/CatalogReducer';
|
||||
import { CatalogLayoutProps } from '../CatalogLayout.types';
|
||||
import { VipGiftItem } from './VipGiftItemView';
|
||||
|
||||
export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { catalogState, dispatchCatalogState } = useCatalogContext();
|
||||
const { catalogOptions = null, setCatalogOptions = null } = useCatalogContext();
|
||||
const { clubGifts = null, subscriptionInfo = null } = catalogOptions;
|
||||
|
||||
const giftsAvailable = useCallback(() =>
|
||||
{
|
||||
const clubGifts = catalogState.clubGifts;
|
||||
|
||||
if(!clubGifts) return '';
|
||||
|
||||
if(clubGifts.giftsAvailable > 0)
|
||||
{
|
||||
return LocalizeText('catalog.club_gift.available', ['amount'], [clubGifts.giftsAvailable.toString()]);
|
||||
}
|
||||
if(clubGifts.giftsAvailable > 0) return LocalizeText('catalog.club_gift.available', [ 'amount' ], [ clubGifts.giftsAvailable.toString() ]);
|
||||
|
||||
if(clubGifts.daysUntilNextGift > 0)
|
||||
{
|
||||
return LocalizeText('catalog.club_gift.days_until_next', ['days'], [clubGifts.daysUntilNextGift.toString()]);
|
||||
}
|
||||
if(clubGifts.daysUntilNextGift > 0) return LocalizeText('catalog.club_gift.days_until_next', [ 'days' ], [ clubGifts.daysUntilNextGift.toString() ]);
|
||||
|
||||
if(catalogState.subscriptionInfo.isVip)
|
||||
{
|
||||
return LocalizeText('catalog.club_gift.not_available');
|
||||
}
|
||||
if(subscriptionInfo.isVip) return LocalizeText('catalog.club_gift.not_available');
|
||||
|
||||
return LocalizeText('catalog.club_gift.no_club');
|
||||
|
||||
}, [catalogState.clubGifts, catalogState.subscriptionInfo.isVip]);
|
||||
}, [ clubGifts, subscriptionInfo ]);
|
||||
|
||||
const selectGift = useCallback((localizationId: string) =>
|
||||
{
|
||||
NotificationUtilities.confirm(LocalizeText('catalog.club_gift.confirm'), () =>
|
||||
{
|
||||
SendMessageHook(new SelectClubGiftComposer(localizationId));
|
||||
const prev = catalogState.clubGifts;
|
||||
|
||||
prev.giftsAvailable--;
|
||||
setCatalogOptions(prevValue =>
|
||||
{
|
||||
prevValue.clubGifts.giftsAvailable--;
|
||||
|
||||
dispatchCatalogState({
|
||||
type: CatalogActions.SET_CLUB_GIFTS,
|
||||
payload: {
|
||||
clubGifts: prev
|
||||
}
|
||||
return { ...prevValue };
|
||||
});
|
||||
}, null);
|
||||
}, [catalogState.clubGifts, dispatchCatalogState]);
|
||||
}, [ setCatalogOptions ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text truncate shrink fontWeight="bold">{ giftsAvailable() }</Text>
|
||||
<Grid columnCount={ 1 } className="nitro-catalog-layout-vip-gifts-grid" overflow="auto">
|
||||
{ (catalogState.clubGifts.offers.length > 0) && catalogState.clubGifts.offers.map(offer => <VipGiftItem key={ offer.offerId } offer={ offer } isAvailable={ (catalogState.clubGifts.getOfferExtraData(offer.offerId).isSelectable && (catalogState.clubGifts.giftsAvailable > 0)) } onSelect={ selectGift }/>) }
|
||||
{ (clubGifts.offers.length > 0) && clubGifts.offers.map(offer => <VipGiftItem key={ offer.offerId } offer={ offer } isAvailable={ (clubGifts.getOfferExtraData(offer.offerId).isSelectable && (clubGifts.giftsAvailable > 0)) } onSelect={ selectGift }/>) }
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
|
@ -22,5 +22,9 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
|
||||
return offer.product.getIconUrl(offer);
|
||||
}, [ offer ]);
|
||||
|
||||
return <LayoutGridItem itemImage={ iconUrl } itemCount={ (offer.pricingModel === Offer.PRICING_MODEL_MULTI) ? offer.product.productCount : 1 } { ...rest } />;
|
||||
const product = offer.product;
|
||||
|
||||
if(!product) return null;
|
||||
|
||||
return <LayoutGridItem itemImage={ iconUrl } itemCount={ ((offer.pricingModel === Offer.PRICING_MODEL_MULTI) ? product.productCount : 1) } itemUniqueSoldout={ (product.uniqueLimitedItemSeriesSize && !product.uniqueLimitedItemsLeft) } itemUniqueNumber={ product.uniqueLimitedItemSeriesSize } { ...rest } />;
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ import { Text } from '../../../../../common/Text';
|
||||
import { BadgeImageView } from '../../../../../views/shared/badge-image/BadgeImageView';
|
||||
import { LimitedEditionCompletePlateView } from '../../../../../views/shared/limited-edition/LimitedEditionCompletePlateView';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { Offer } from '../../../common/Offer';
|
||||
import { CatalogRoomPreviewerView } from '../../catalog-room-previewer/CatalogRoomPreviewerView';
|
||||
import { CatalogPurchaseView } from '../purchase/CatalogPurchaseView';
|
||||
|
||||
export interface CatalogProductPreviewViewProps
|
||||
@ -28,8 +26,6 @@ export const CatalogProductPreviewView: FC<CatalogProductPreviewViewProps> = pro
|
||||
return (
|
||||
<>
|
||||
<Column overflow="hidden" position="relative" gap={ 0 }>
|
||||
{ ((offer.pricingModel === Offer.PRICING_MODEL_SINGLE) || (offer.pricingModel === Offer.PRICING_MODEL_MULTI)) &&
|
||||
<CatalogRoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } /> }
|
||||
{ product.isUniqueLimitedItem &&
|
||||
<Base fullWidth position="absolute" className="top-1">
|
||||
<LimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={ product.uniqueLimitedItemsLeft } uniqueLimitedSeriesSize={ product.uniqueLimitedItemSeriesSize } />
|
||||
|
@ -1,52 +0,0 @@
|
||||
import { GetProductOfferComposer, MouseEventType } from '@nitrots/nitro-renderer';
|
||||
import { FC, MouseEvent, useCallback, useState } from 'react';
|
||||
import { SendMessageHook } from '../../../../../hooks';
|
||||
import { FurnitureOffer } from '../../../common/FurnitureOffer';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogProductView } from '../product/CatalogProductView';
|
||||
|
||||
export interface CatalogPageOfferViewProps
|
||||
{
|
||||
isActive: boolean;
|
||||
offer: IPurchasableOffer;
|
||||
}
|
||||
|
||||
export const CatalogPageOfferView: FC<CatalogPageOfferViewProps> = props =>
|
||||
{
|
||||
const { isActive = false, offer = null } = props;
|
||||
const [ isMouseDown, setMouseDown ] = useState(false);
|
||||
const { setCurrentOffer = null } = useCatalogContext();
|
||||
|
||||
const onMouseEvent = useCallback((event: MouseEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case MouseEventType.MOUSE_CLICK:
|
||||
if(isActive) return;
|
||||
|
||||
setCurrentOffer(offer);
|
||||
|
||||
if(offer instanceof FurnitureOffer)
|
||||
{
|
||||
SendMessageHook(new GetProductOfferComposer(offer.offerId));
|
||||
}
|
||||
return;
|
||||
case MouseEventType.MOUSE_DOWN:
|
||||
setMouseDown(true);
|
||||
return;
|
||||
case MouseEventType.MOUSE_UP:
|
||||
setMouseDown(false);
|
||||
return;
|
||||
case MouseEventType.ROLL_OUT:
|
||||
if(!isMouseDown || !isActive) return;
|
||||
return;
|
||||
}
|
||||
}, [ isActive, offer, isMouseDown, setCurrentOffer ]);
|
||||
|
||||
const product = offer.product;
|
||||
|
||||
if(!product) return null;
|
||||
|
||||
return <CatalogProductView itemActive={ isActive } product={ product } onClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } />
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { FC } from 'react';
|
||||
import { Grid, GridProps } from '../../../../../common/Grid';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogPageOfferView } from './CatalogPageOfferView';
|
||||
|
||||
export interface CatalogPageOffersViewProps extends GridProps
|
||||
{
|
||||
offers: IPurchasableOffer[];
|
||||
}
|
||||
|
||||
export const CatalogPageOffersView: FC<CatalogPageOffersViewProps> = props =>
|
||||
{
|
||||
const { offers = [], children = null, ...rest } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
return (
|
||||
<Grid grow columnCount={ 5 } overflow="auto" { ...rest }>
|
||||
{ offers && (offers.length > 0) && offers.map((offer, index) => <CatalogPageOfferView key={ index } isActive={ (currentOffer === offer) } offer={ offer } />) }
|
||||
{ children }
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
import { PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Button, ButtonProps } from '../../../../../common/Button';
|
||||
import { CatalogEvent } from '../../../../../events';
|
||||
import { BatchUpdates } from '../../../../../hooks';
|
||||
import { useUiEvent } from '../../../../../hooks/events/ui/ui-event';
|
||||
import { SendMessageHook } from '../../../../../hooks/messages/message-event';
|
||||
import { LoadingSpinnerView } from '../../../../../layout';
|
||||
import { GetCurrencyAmount } from '../../../../../views/purse/common/CurrencyHelper';
|
||||
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
|
||||
export interface CatalogPurchaseButtonViewProps extends ButtonProps
|
||||
{
|
||||
offer: IPurchasableOffer;
|
||||
pageId: number;
|
||||
extra?: string;
|
||||
quantity?: number;
|
||||
isPurchaseAllowed?: boolean;
|
||||
beforePurchase?: () => void;
|
||||
}
|
||||
|
||||
export const CatalogPurchaseButtonView: FC<CatalogPurchaseButtonViewProps> = props =>
|
||||
{
|
||||
const { offer = null, pageId = -1, extra = null, quantity = 1, isPurchaseAllowed = true, beforePurchase = null, ...rest } = props;
|
||||
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
|
||||
const [ pendingApproval, setPendingApproval ] = useState(false);
|
||||
|
||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case CatalogEvent.PURCHASE_SUCCESS:
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
return;
|
||||
case CatalogEvent.SOLD_OUT:
|
||||
setPurchaseState(CatalogPurchaseState.SOLD_OUT);
|
||||
return;
|
||||
case CatalogEvent.PURCHASE_FAILED:
|
||||
setPurchaseState(CatalogPurchaseState.FAILED);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.SOLD_OUT, onCatalogEvent);
|
||||
useUiEvent(CatalogEvent.PURCHASE_FAILED, onCatalogEvent);
|
||||
|
||||
const purchase = useCallback(() =>
|
||||
{
|
||||
SendMessageHook(new PurchaseFromCatalogComposer(pageId, offer.offerId, extra, quantity));
|
||||
}, [ pageId, offer, extra, quantity ]);
|
||||
|
||||
const attemptPurchase = useCallback(() =>
|
||||
{
|
||||
setPurchaseState(CatalogPurchaseState.PURCHASE);
|
||||
|
||||
if(beforePurchase) beforePurchase();
|
||||
|
||||
if(!isPurchaseAllowed)
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setPendingApproval(true);
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
purchase();
|
||||
}, [ isPurchaseAllowed, beforePurchase, purchase ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
}, [ offer, quantity ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(pendingApproval && isPurchaseAllowed)
|
||||
{
|
||||
setPendingApproval(false);
|
||||
|
||||
purchase();
|
||||
|
||||
return;
|
||||
}
|
||||
}, [ purchaseState, pendingApproval, isPurchaseAllowed, purchase ]);
|
||||
|
||||
const product = offer.product;
|
||||
|
||||
if(product && product.isUniqueLimitedItem && !product.uniqueLimitedItemsLeft)
|
||||
{
|
||||
return <Button variant="danger" size="sm" disabled>{ LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
}
|
||||
|
||||
if((offer.priceInCredits * quantity) > GetCurrencyAmount(-1))
|
||||
{
|
||||
return <Button variant="danger" size="sm" disabled>{ LocalizeText('catalog.alert.notenough.title') }</Button>;
|
||||
}
|
||||
|
||||
if((offer.priceInActivityPoints * quantity) > GetCurrencyAmount(offer.activityPointType))
|
||||
{
|
||||
return <Button variant="danger" size="sm" disabled>{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + offer.activityPointType) }</Button>;
|
||||
}
|
||||
|
||||
switch(purchaseState)
|
||||
{
|
||||
case CatalogPurchaseState.CONFIRM:
|
||||
return <Button variant="warning" size="sm" onClick={ attemptPurchase } { ...rest }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>;
|
||||
case CatalogPurchaseState.PURCHASE:
|
||||
return <Button variant="primary" size="sm" disabled { ...rest }><LoadingSpinnerView /></Button>;
|
||||
case CatalogPurchaseState.SOLD_OUT:
|
||||
return <Button variant="danger" size="sm" disabled { ...rest }>{ LocalizeText('generic.failed') + ' - ' + LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
case CatalogPurchaseState.FAILED:
|
||||
return <Button variant="danger" size="sm" disabled { ...rest }>{ LocalizeText('generic.failed') }</Button>;
|
||||
case CatalogPurchaseState.NONE:
|
||||
default:
|
||||
return <Button variant="success" size="sm" onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) } { ...rest }>{ LocalizeText('buy') }</Button>
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Button, ButtonProps } from '../../../../../common/Button';
|
||||
import { CatalogInitGiftEvent } from '../../../../../events/catalog/CatalogInitGiftEvent';
|
||||
import { dispatchUiEvent } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
|
||||
export interface CatalogPurchaseGiftButtonViewProps extends ButtonProps
|
||||
{
|
||||
offer: IPurchasableOffer;
|
||||
pageId: number;
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
export const CatalogPurchaseGiftButtonView: FC<CatalogPurchaseGiftButtonViewProps> = props =>
|
||||
{
|
||||
const { offer = null, pageId = -1, extra = null, ...rest } = props;
|
||||
|
||||
const initGift = () =>
|
||||
{
|
||||
dispatchUiEvent(new CatalogInitGiftEvent(pageId, offer.offerId, extra));
|
||||
}
|
||||
|
||||
return <Button variant="secondary" size="sm" onClick={ initGift } { ...rest }>{ LocalizeText('catalog.purchase_confirmation.gift') }</Button>;
|
||||
}
|
@ -7,8 +7,6 @@ import { Text } from '../../../../../common/Text';
|
||||
import { CurrencyIcon } from '../../../../../views/shared/currency-icon/CurrencyIcon';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { Offer } from '../../../common/Offer';
|
||||
import { CatalogPurchaseButtonView } from './CatalogPurchaseButtonView';
|
||||
import { CatalogPurchaseGiftButtonView } from './CatalogPurchaseGiftButtonView';
|
||||
|
||||
export interface CatalogPurchaseViewProps
|
||||
{
|
||||
@ -82,11 +80,6 @@ export const CatalogPurchaseView: FC<CatalogPurchaseViewProps> = props =>
|
||||
</Flex> }
|
||||
</Column>
|
||||
</Flex>
|
||||
<Column gap={ 1 }>
|
||||
<CatalogPurchaseButtonView offer={ offer } pageId={ pageId } extra={ extraData } quantity={ quantity } disabled={ disabled } />
|
||||
{ offer.giftable &&
|
||||
<CatalogPurchaseGiftButtonView offer={ offer } pageId={ pageId } extra={ extraData } disabled={ disabled } /> }
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { FC, useCallback, useState } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { BaseProps } from '../../../../../common/Base';
|
||||
import { CatalogSelectProductEvent } from '../../../../../events';
|
||||
import { CatalogWidgetEvent } from '../../../../../events/catalog/CatalogWidgetEvent';
|
||||
import { useUiEvent } from '../../../../../hooks';
|
||||
import { BadgeImageView } from '../../../../../views/shared/badge-image/BadgeImageView';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
interface CatalogAddOnBadgeWidgetViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
@ -13,16 +11,9 @@ interface CatalogAddOnBadgeWidgetViewProps extends BaseProps<HTMLDivElement>
|
||||
export const CatalogAddOnBadgeWidgetView: FC<CatalogAddOnBadgeWidgetViewProps> = props =>
|
||||
{
|
||||
const { ...rest } = props;
|
||||
const [ badgeCode, setBadgeCode ] = useState<string>(null);
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
const onCatalogSelectProductEvent = useCallback((event: CatalogSelectProductEvent) =>
|
||||
{
|
||||
if(event.offer.badgeCode) setBadgeCode(event.offer.badgeCode);
|
||||
}, []);
|
||||
if(!currentOffer || !currentOffer.badgeCode || !currentOffer.badgeCode.length) return null;
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SELECT_PRODUCT, onCatalogSelectProductEvent);
|
||||
|
||||
if(!badgeCode || !badgeCode.length) return null;
|
||||
|
||||
return <BadgeImageView badgeCode={ badgeCode } { ...rest } />;
|
||||
return <BadgeImageView badgeCode={ currentOffer.badgeCode } { ...rest } />;
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
import { StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Grid, GridProps } from '../../../../../common/Grid';
|
||||
import { LayoutGridItem } from '../../../../../common/layout/LayoutGridItem';
|
||||
import { InventoryBadgesUpdatedEvent } from '../../../../../events';
|
||||
import { InventoryBadgesRequestEvent } from '../../../../../events/inventory/InventoryBadgesRequestEvent';
|
||||
import { dispatchUiEvent, useUiEvent } from '../../../../../hooks';
|
||||
import { BadgeImageView } from '../../../../../views/shared/badge-image/BadgeImageView';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
const EXCLUDED_BADGE_CODES: string[] = [];
|
||||
|
||||
interface CatalogBadgeSelectorWidgetViewProps extends GridProps
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
export const CatalogBadgeSelectorWidgetView: FC<CatalogBadgeSelectorWidgetViewProps> = props =>
|
||||
{
|
||||
const { grow = true, columnCount = 5, overflow = 'auto', ...rest } = props;
|
||||
const [ badges, setBadges ] = useState<string[]>([]);
|
||||
const [ currentBadge, setCurrentBadge ] = useState<string>(null);
|
||||
const { currentOffer = null, setPurchaseOptions = null } = useCatalogContext();
|
||||
|
||||
const onInventoryBadgesUpdatedEvent = useCallback((event: InventoryBadgesUpdatedEvent) =>
|
||||
{
|
||||
setBadges(event.badges);
|
||||
}, []);
|
||||
|
||||
useUiEvent(InventoryBadgesUpdatedEvent.BADGES_UPDATED, onInventoryBadgesUpdatedEvent);
|
||||
|
||||
const previewStuffData = useMemo(() =>
|
||||
{
|
||||
if(!currentBadge) return null;
|
||||
|
||||
const stuffData = new StringDataType();
|
||||
|
||||
stuffData.setValue([ '0', currentBadge, '', '' ]);
|
||||
|
||||
return stuffData;
|
||||
}, [ currentBadge ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
setPurchaseOptions(prevValue =>
|
||||
{
|
||||
const extraParamRequired = true;
|
||||
const extraData = ((previewStuffData && previewStuffData.getValue(1)) || null);
|
||||
|
||||
return { ...prevValue, extraParamRequired, extraData, previewStuffData };
|
||||
});
|
||||
}, [ currentOffer, previewStuffData, setPurchaseOptions ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
dispatchUiEvent(new InventoryBadgesRequestEvent(InventoryBadgesRequestEvent.REQUEST_BADGES));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Grid grow={ grow } columnCount={ columnCount } overflow={ overflow } { ...rest }>
|
||||
{ badges && (badges.length > 0) && badges.map(code =>
|
||||
{
|
||||
return (
|
||||
<LayoutGridItem key={ code } itemActive={ (currentBadge === code) } onClick={ event => setCurrentBadge(code) }>
|
||||
<BadgeImageView badgeCode={ code } />
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { Grid, GridProps } from '../../../../../common/Grid';
|
||||
import { LayoutGridItem } from '../../../../../common/layout/LayoutGridItem';
|
||||
import { CatalogPageReadyEvent, CatalogSelectProductEvent } from '../../../../../events';
|
||||
import { CatalogWidgetEvent } from '../../../../../events/catalog/CatalogWidgetEvent';
|
||||
import { dispatchUiEvent, useUiEvent } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
interface CatalogBundleGridWidgetViewProps extends GridProps
|
||||
@ -15,37 +11,13 @@ interface CatalogBundleGridWidgetViewProps extends GridProps
|
||||
export const CatalogBundleGridWidgetView: FC<CatalogBundleGridWidgetViewProps> = props =>
|
||||
{
|
||||
const { children = null, ...rest } = props;
|
||||
const [ offer, setOffer ] = useState<IPurchasableOffer>(null);
|
||||
const { currentPage = null } = useCatalogContext();
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
const onCatalogSelectProductEvent = useCallback((event: CatalogSelectProductEvent) =>
|
||||
{
|
||||
setOffer(event.offer);
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SELECT_PRODUCT, onCatalogSelectProductEvent);
|
||||
|
||||
const onCatalogPageReadyEvent = useCallback((event: CatalogPageReadyEvent) =>
|
||||
{
|
||||
if(!currentPage || (currentPage.offers.length !== 1)) return;
|
||||
|
||||
const offer = currentPage.offers[0];
|
||||
|
||||
dispatchUiEvent(new CatalogSelectProductEvent(offer));
|
||||
}, [ currentPage ]);
|
||||
|
||||
useUiEvent(CatalogPageReadyEvent.PAGE_READY, onCatalogPageReadyEvent);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
return () => setOffer(null);
|
||||
}, [ currentPage ]);
|
||||
|
||||
if(!offer) return null;
|
||||
if(!currentOffer) return null;
|
||||
|
||||
return (
|
||||
<Grid grow columnCount={ 5 } overflow="auto" { ...rest }>
|
||||
{ offer.products && (offer.products.length > 0) && offer.products.map((product, index) => <LayoutGridItem key={ index } itemImage={ product.getIconUrl() } itemCount={ product.productCount } />) }
|
||||
{ currentOffer.products && (currentOffer.products.length > 0) && currentOffer.products.map((product, index) => <LayoutGridItem key={ index } itemImage={ product.getIconUrl() } itemCount={ product.productCount } />) }
|
||||
{ children }
|
||||
</Grid>
|
||||
);
|
||||
|
@ -1,20 +1,16 @@
|
||||
import { FC, useCallback } from 'react';
|
||||
import { CatalogPageReadyEvent, CatalogSelectProductEvent } from '../../../../../events';
|
||||
import { dispatchUiEvent, useUiEvent } from '../../../../../hooks';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
export const CatalogFirstProductSelectorWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const { currentPage = null } = useCatalogContext();
|
||||
const { currentPage = null, setCurrentOffer = null } = useCatalogContext();
|
||||
|
||||
const onCatalogPageReadyEvent = useCallback((event: CatalogPageReadyEvent) =>
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentPage || !currentPage.offers.length) return;
|
||||
|
||||
dispatchUiEvent(new CatalogSelectProductEvent(currentPage.offers[0]));
|
||||
}, [ currentPage ]);
|
||||
|
||||
useUiEvent(CatalogPageReadyEvent.PAGE_READY, onCatalogPageReadyEvent);
|
||||
setCurrentOffer(currentPage.offers[0]);
|
||||
}, [ currentPage, setCurrentOffer ]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
import { StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { BaseProps } from '../../../../../common/Base';
|
||||
import { BadgeImageView } from '../../../../../views/shared/badge-image/BadgeImageView';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
interface CatalogGuildBadgeWidgetViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
export const CatalogGuildBadgeWidgetView: FC<CatalogGuildBadgeWidgetViewProps> = props =>
|
||||
{
|
||||
const { ...rest } = props;
|
||||
const { currentOffer = null, purchaseOptions = null } = useCatalogContext();
|
||||
const { previewStuffData = null } = purchaseOptions;
|
||||
|
||||
const badgeCode = useMemo(() =>
|
||||
{
|
||||
if(!currentOffer || !previewStuffData) return null;
|
||||
|
||||
const badgeCode = (previewStuffData as StringDataType).getValue(2);
|
||||
|
||||
if(!badgeCode || !badgeCode.length) return null;
|
||||
|
||||
return badgeCode;
|
||||
}, [ currentOffer, previewStuffData ]);
|
||||
|
||||
if(!badgeCode) return null;
|
||||
|
||||
return <BadgeImageView badgeCode={ badgeCode } isGroup={ true } { ...rest } />;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import { CatalogGroupsComposer, StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Base } from '../../../../../common/Base';
|
||||
import { Button } from '../../../../../common/Button';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { SendMessageHook } from '../../../../../hooks';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
|
||||
const { currentOffer = null, catalogOptions = null, setPurchaseOptions = null } = useCatalogContext();
|
||||
const { groups = null } = catalogOptions;
|
||||
|
||||
const previewStuffData = useMemo(() =>
|
||||
{
|
||||
if(!groups || !groups.length) return null;
|
||||
|
||||
const group = groups[selectedGroupIndex];
|
||||
|
||||
if(!group) return null;
|
||||
|
||||
const stuffData = new StringDataType();
|
||||
|
||||
stuffData.setValue([ '0', group.groupId.toString(), group.badgeCode, group.colorA, group.colorB ]);
|
||||
|
||||
return stuffData;
|
||||
}, [ selectedGroupIndex, groups ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
setPurchaseOptions(prevValue =>
|
||||
{
|
||||
const extraParamRequired = true;
|
||||
const extraData = ((previewStuffData && previewStuffData.getValue(1)) || null);
|
||||
|
||||
return { ...prevValue, extraParamRequired, extraData, previewStuffData };
|
||||
});
|
||||
}, [ currentOffer, previewStuffData, setPurchaseOptions ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageHook(new CatalogGroupsComposer());
|
||||
}, []);
|
||||
|
||||
if(!groups || !groups.length)
|
||||
{
|
||||
return (
|
||||
<Base className="bg-muted rounded p-1 text-black text-center">
|
||||
{ LocalizeText('catalog.guild_selector.members_only') }
|
||||
<Button className="mt-1">
|
||||
{ LocalizeText('catalog.guild_selector.find_groups') }
|
||||
</Button>
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
||||
const selectedGroup = groups[selectedGroupIndex];
|
||||
|
||||
return (
|
||||
<Flex gap={ 1 }>
|
||||
{ !!selectedGroup &&
|
||||
<Flex overflow="hidden" className="rounded border">
|
||||
<Base fullHeight style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorA } } />
|
||||
<Base fullHeight style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorB } } />
|
||||
</Flex> }
|
||||
<select className="form-select form-select-sm" value={ selectedGroupIndex } onChange={ event => setSelectedGroupIndex(parseInt(event.target.value)) }>
|
||||
{ groups.map((group, index) => <option key={ index } value={ index }>{ group.groupName }</option>) }
|
||||
</select>
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { FC } from 'react';
|
||||
import { Grid, GridProps } from '../../../../../common/Grid';
|
||||
import { CatalogSelectProductEvent, CatalogSetExtraPurchaseParameterEvent } from '../../../../../events';
|
||||
import { CatalogSetExtraPurchaseParameterEvent } from '../../../../../events';
|
||||
import { dispatchUiEvent } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { ProductTypeEnum } from '../../../common/ProductTypeEnum';
|
||||
@ -23,8 +23,6 @@ export const CatalogItemGridWidgetView: FC<CatalogItemGridWidgetViewProps> = pro
|
||||
{
|
||||
setCurrentOffer(offer);
|
||||
|
||||
dispatchUiEvent(new CatalogSelectProductEvent(offer));
|
||||
|
||||
if(offer.product && (offer.product.productType === ProductTypeEnum.WALL))
|
||||
{
|
||||
dispatchUiEvent(new CatalogSetExtraPurchaseParameterEvent(offer.product.extraParam));
|
||||
@ -35,7 +33,7 @@ export const CatalogItemGridWidgetView: FC<CatalogItemGridWidgetViewProps> = pro
|
||||
<Grid grow columnCount={ 5 } overflow="auto" { ...rest }>
|
||||
{ currentPage.offers && (currentPage.offers.length > 0) && currentPage.offers.map((offer, index) =>
|
||||
{
|
||||
return <CatalogGridOfferView key={ index } itemActive={ (currentOffer === offer) } offer={ offer } onClick={ event => selectOffer(offer) } />;
|
||||
return <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer.offerId === offer.offerId)) } offer={ offer } onClick={ event => selectOffer(offer) } />;
|
||||
}) }
|
||||
{ children }
|
||||
</Grid>
|
||||
|
@ -0,0 +1,20 @@
|
||||
import { FC } from 'react';
|
||||
import { Base, BaseProps } from '../../../../../common/Base';
|
||||
import { LimitedEditionCompletePlateView } from '../../../../../views/shared/limited-edition/LimitedEditionCompletePlateView';
|
||||
import { Offer } from '../../../common/Offer';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
export const CatalogLimitedItemWidgetView: FC<BaseProps<HTMLDivElement>> = props =>
|
||||
{
|
||||
const { children = null, ...rest } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
if(!currentOffer || (currentOffer.pricingModel !== Offer.PRICING_MODEL_SINGLE) || !currentOffer.product.isUniqueLimitedItem) return null;
|
||||
|
||||
return (
|
||||
<Base { ...rest }>
|
||||
<LimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={ currentOffer.product.uniqueLimitedItemsLeft } uniqueLimitedSeriesSize={ currentOffer.product.uniqueLimitedItemSeriesSize } />
|
||||
{ children }
|
||||
</Base>
|
||||
);
|
||||
}
|
@ -4,32 +4,36 @@ import { Flex } from '../../../../../common/Flex';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { CurrencyIcon } from '../../../../../views/shared/currency-icon/CurrencyIcon';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
interface CatalogPriceDisplayWidgetViewProps
|
||||
{
|
||||
offer: IPurchasableOffer;
|
||||
separator?: boolean;
|
||||
}
|
||||
|
||||
export const CatalogPriceDisplayWidgetView: FC<CatalogPriceDisplayWidgetViewProps> = props =>
|
||||
{
|
||||
const { offer = null } = props;
|
||||
const { offer = null, separator = false } = props;
|
||||
const { purchaseOptions = null } = useCatalogContext();
|
||||
const { quantity = 1 } = purchaseOptions;
|
||||
|
||||
if(!offer) return null;
|
||||
|
||||
return (
|
||||
<Flex gap={ 1 } className="bg-muted p-1 rounded">
|
||||
<>
|
||||
{ (offer.priceInCredits > 0) &&
|
||||
<Flex alignItems="center" justifyContent="end" gap={ 1 }>
|
||||
<Text>{ offer.priceInCredits }</Text>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<Text bold>{ (offer.priceInCredits * quantity) }</Text>
|
||||
<CurrencyIcon type={ -1 } />
|
||||
</Flex> }
|
||||
{ ( offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) &&
|
||||
<FontAwesomeIcon icon="plus" /> }
|
||||
{ separator && (offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) &&
|
||||
<FontAwesomeIcon size="xs" color="black" icon="plus" /> }
|
||||
{ (offer.priceInActivityPoints > 0) &&
|
||||
<Flex alignItems="center" justifyContent="end" gap={ 1 }>
|
||||
<Text>{ offer.priceInActivityPoints }</Text>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<Text bold>{ (offer.priceInActivityPoints * quantity) }</Text>
|
||||
<CurrencyIcon type={ offer.activityPointType } />
|
||||
</Flex> }
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { IObjectData } from '@nitrots/nitro-renderer';
|
||||
import { PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { CreateLinkEvent, GetClubMemberLevel, LocalizeText } from '../../../../../api';
|
||||
import { Button } from '../../../../../common/Button';
|
||||
import { CatalogSelectProductEvent, CatalogSetExtraPurchaseParameterEvent } from '../../../../../events';
|
||||
import { CatalogEvent, CatalogPurchasedEvent, CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent } from '../../../../../events';
|
||||
import { CatalogInitPurchaseEvent } from '../../../../../events/catalog/CatalogInitPurchaseEvent';
|
||||
import { CatalogPurchaseOverrideEvent } from '../../../../../events/catalog/CatalogPurchaseOverrideEvent';
|
||||
import { CatalogSetRoomPreviewerStuffDataEvent } from '../../../../../events/catalog/CatalogSetRoomPreviewerStuffDataEvent';
|
||||
import { CatalogWidgetEvent } from '../../../../../events/catalog/CatalogWidgetEvent';
|
||||
import { useUiEvent } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { SendMessageHook, useUiEvent } from '../../../../../hooks';
|
||||
import { LoadingSpinnerView } from '../../../../../layout';
|
||||
import { GetCurrencyAmount } from '../../../../../views/purse/common/CurrencyHelper';
|
||||
import { CatalogPurchaseState } from '../../../common/CatalogPurchaseState';
|
||||
import { Offer } from '../../../common/Offer';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
interface CatalogPurchaseWidgetViewProps
|
||||
{
|
||||
@ -19,103 +20,140 @@ interface CatalogPurchaseWidgetViewProps
|
||||
export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = props =>
|
||||
{
|
||||
const { noGiftOption = false } = props;
|
||||
const [ offer, setOffer ] = useState<IPurchasableOffer>(null);
|
||||
const [ quantity, setQuantity ] = useState(1);
|
||||
const [ extraData, setExtraData ] = useState<string>('');
|
||||
const [ extraParamRequired, setExtraParamRequired ] = useState(false);
|
||||
const [ giftEnabled, setGiftEnabled ] = useState(false);
|
||||
const [ purchaseCallback, setPurchaseCallback ] = useState<Function>(null);
|
||||
const [ previewStuffData, setPreviewStuffData ] = useState<IObjectData>(null);
|
||||
const [ purchaseWillBeGift, setPurchaseWillBeGift ] = useState(false);
|
||||
|
||||
const onCatalogSelectProductEvent = useCallback((event: CatalogSelectProductEvent) =>
|
||||
{
|
||||
setOffer(event.offer);
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SELECT_PRODUCT, onCatalogSelectProductEvent);
|
||||
|
||||
const onCatalogSetExtraPurchaseParameterEvent = useCallback((event: CatalogSetExtraPurchaseParameterEvent) =>
|
||||
{
|
||||
setExtraData(event.parameter);
|
||||
setGiftEnabled(offer && offer.giftable);
|
||||
}, [ offer ]);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SET_EXTRA_PARM, onCatalogSetExtraPurchaseParameterEvent);
|
||||
|
||||
const onCatalogPurchaseOverrideEvent = useCallback((event: CatalogPurchaseOverrideEvent) =>
|
||||
{
|
||||
setPurchaseCallback(event.callback);
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.PURCHASE_OVERRIDE, onCatalogPurchaseOverrideEvent);
|
||||
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
|
||||
const { currentOffer = null, currentPage = null, purchaseOptions = null, setPurchaseOptions = null, getNodesByOfferId = null } = useCatalogContext();
|
||||
const { quantity = 1, extraData = '', extraParamRequired = false, purchaseCallback = null, previewStuffData = null } = purchaseOptions;
|
||||
|
||||
const onCatalogInitPurchaseEvent = useCallback((event: CatalogInitPurchaseEvent) =>
|
||||
{
|
||||
if(!offer) return;
|
||||
if(!currentOffer) return;
|
||||
|
||||
// show purchase confirmation
|
||||
// offer, page.pageId, extraData, quantity, previewStuffData, null, true, null
|
||||
}, [ offer ]);
|
||||
}, [ currentOffer ]);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.INIT_PURCHASE, onCatalogInitPurchaseEvent);
|
||||
|
||||
const onCatalogSetRoomPreviewerStuffDataEvent = useCallback((event: CatalogSetRoomPreviewerStuffDataEvent) =>
|
||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||
{
|
||||
setPreviewStuffData(event.stuffData);
|
||||
switch(event.type)
|
||||
{
|
||||
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
return;
|
||||
case CatalogPurchaseFailureEvent.PURCHASE_FAILED:
|
||||
setPurchaseState(CatalogPurchaseState.FAILED);
|
||||
return;
|
||||
case CatalogPurchaseNotAllowedEvent.NOT_ALLOWED:
|
||||
setPurchaseState(CatalogPurchaseState.FAILED);
|
||||
return;
|
||||
case CatalogPurchaseSoldOutEvent.SOLD_OUT:
|
||||
setPurchaseState(CatalogPurchaseState.SOLD_OUT);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SET_PREVIEWER_STUFFDATA, onCatalogSetRoomPreviewerStuffDataEvent);
|
||||
|
||||
const onCatalogWidgetEvent = useCallback((event: CatalogWidgetEvent) =>
|
||||
{
|
||||
setExtraParamRequired(true);
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.EXTRA_PARAM_REQUIRED_FOR_BUY, onCatalogWidgetEvent);
|
||||
useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchaseFailureEvent.PURCHASE_FAILED, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchaseNotAllowedEvent.NOT_ALLOWED, onCatalogEvent);
|
||||
useUiEvent(CatalogPurchaseSoldOutEvent.SOLD_OUT, onCatalogEvent);
|
||||
|
||||
const isLimitedSoldOut = useMemo(() =>
|
||||
{
|
||||
if(!offer) return false;
|
||||
if(!currentOffer) return false;
|
||||
|
||||
if(extraParamRequired && (!extraData || !extraData.length)) return false;
|
||||
|
||||
if(offer.pricingModel === Offer.PRICING_MODEL_SINGLE)
|
||||
if(currentOffer.pricingModel === Offer.PRICING_MODEL_SINGLE)
|
||||
{
|
||||
const product = offer.product;
|
||||
const product = currentOffer.product;
|
||||
|
||||
if(product && product.isUniqueLimitedItem) return !product.uniqueLimitedItemsLeft;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [ offer, extraParamRequired, extraData ]);
|
||||
}, [ currentOffer, extraParamRequired, extraData ]);
|
||||
|
||||
const purchase = useCallback((isGift: boolean = false) =>
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
}, []);
|
||||
if(GetClubMemberLevel() < currentOffer.clubLevel)
|
||||
{
|
||||
CreateLinkEvent('habboUI/open/hccenter');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(purchaseCallback)
|
||||
{
|
||||
purchaseCallback();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let pageId = currentOffer.page.pageId;
|
||||
|
||||
if(pageId === -1)
|
||||
{
|
||||
const nodes = getNodesByOfferId(currentOffer.offerId);
|
||||
|
||||
if(nodes) pageId = nodes[0].pageId;
|
||||
}
|
||||
|
||||
SendMessageHook(new PurchaseFromCatalogComposer(pageId, currentOffer.offerId, extraData, quantity));
|
||||
}, [ currentOffer, purchaseCallback, extraData, quantity, getNodesByOfferId ]);
|
||||
|
||||
// dispatchUiEvent(new CatalogInitGiftEvent(pageId, offer.offerId, extra)); setup gift
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setQuantity(1);
|
||||
setPurchaseWillBeGift(false);
|
||||
}, [ offer ]);
|
||||
if(!currentOffer) return;
|
||||
|
||||
if(!offer) return null;
|
||||
|
||||
const getPurchaseButton = () =>
|
||||
return () =>
|
||||
{
|
||||
const priceCredits = (offer.priceInCredits * quantity);
|
||||
const pricePoints = (offer.priceInActivityPoints * quantity);
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
setPurchaseOptions({ quantity: 1, extraData: '', extraParamRequired: false, purchaseCallback: null, previewStuffData: null });
|
||||
}
|
||||
}, [ currentOffer, setPurchaseOptions ]);
|
||||
|
||||
if(!currentOffer) return null;
|
||||
|
||||
const PurchaseButton = () =>
|
||||
{
|
||||
const priceCredits = (currentOffer.priceInCredits * quantity);
|
||||
const pricePoints = (currentOffer.priceInActivityPoints * quantity);
|
||||
|
||||
if(GetClubMemberLevel() < currentOffer.clubLevel) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.hc.required') }</Button>;
|
||||
|
||||
if(isLimitedSoldOut) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
|
||||
if(priceCredits > GetCurrencyAmount(-1)) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.notenough.title') }</Button>;
|
||||
|
||||
if(pricePoints > GetCurrencyAmount(currentOffer.activityPointType)) return <Button variant="danger" disabled>{ LocalizeText('catalog.alert.notenough.activitypoints.title.' + currentOffer.activityPointType) }</Button>;
|
||||
|
||||
switch(purchaseState)
|
||||
{
|
||||
case CatalogPurchaseState.CONFIRM:
|
||||
return <Button onClick={ event => purchase() }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>;
|
||||
case CatalogPurchaseState.PURCHASE:
|
||||
return <Button disabled><LoadingSpinnerView /></Button>;
|
||||
case CatalogPurchaseState.FAILED:
|
||||
return <Button variant="danger">{ LocalizeText('generic.failed') }</Button>;
|
||||
case CatalogPurchaseState.SOLD_OUT:
|
||||
return <Button variant="danger">{ LocalizeText('generic.failed') + ' - ' + LocalizeText('catalog.alert.limited_edition_sold_out.title') }</Button>;
|
||||
case CatalogPurchaseState.NONE:
|
||||
default:
|
||||
return <Button disabled={ (extraParamRequired && (!extraData || !extraData.length)) } onClick={ event => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('catalog.purchase_confirmation.' + (currentOffer.isRentOffer ? 'rent' : 'buy')) }</Button>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button disabled={ (isLimitedSoldOut || (extraParamRequired && (!extraData || !extraData.length))) }>
|
||||
{ LocalizeText('catalog.purchase_confirmation.' + (offer.isRentOffer ? 'rent' : 'buy')) }
|
||||
</Button>
|
||||
{ (!noGiftOption && !offer.isRentOffer) &&
|
||||
<Button disabled={ ((quantity > 1) || !offer.giftable || isLimitedSoldOut || (extraParamRequired && (!extraData || !extraData.length))) }>
|
||||
<PurchaseButton />
|
||||
{ (!noGiftOption && !currentOffer.isRentOffer) &&
|
||||
<Button disabled={ ((quantity > 1) || !currentOffer.giftable || isLimitedSoldOut || (extraParamRequired && (!extraData || !extraData.length))) }>
|
||||
{ LocalizeText('catalog.purchase_confirmation.gift') }
|
||||
</Button> }
|
||||
</>
|
||||
|
@ -1,27 +1,21 @@
|
||||
import { FC, useCallback, useState } from 'react';
|
||||
import { BaseProps } from '../../../../../common/Base';
|
||||
import { CatalogSelectProductEvent } from '../../../../../events';
|
||||
import { CatalogWidgetEvent } from '../../../../../events/catalog/CatalogWidgetEvent';
|
||||
import { useUiEvent } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { FC } from 'react';
|
||||
import { Flex, FlexProps } from '../../../../../common/Flex';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogPriceDisplayWidgetView } from './CatalogPriceDisplayWidgetView';
|
||||
|
||||
interface CatalogSimplePriceWidgetViewProps extends BaseProps<HTMLDivElement>
|
||||
interface CatalogSimplePriceWidgetViewProps extends FlexProps
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
export const CatalogSimplePriceWidgetView: FC<CatalogSimplePriceWidgetViewProps> = props =>
|
||||
{
|
||||
const { ...rest } = props;
|
||||
const [ offer, setOffer ] = useState<IPurchasableOffer>(null);
|
||||
const { gap = 1, ...rest } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
const onCatalogSelectProductEvent = useCallback((event: CatalogSelectProductEvent) =>
|
||||
{
|
||||
setOffer(event.offer);
|
||||
}, []);
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SELECT_PRODUCT, onCatalogSelectProductEvent);
|
||||
|
||||
return <CatalogPriceDisplayWidgetView offer={ offer } { ...rest } />;
|
||||
return (
|
||||
<Flex gap={ gap } alignItems="center" classNames={ [ 'bg-muted', 'p-1', 'rounded' ] } { ...rest }>
|
||||
<CatalogPriceDisplayWidgetView separator={ true } offer={ currentOffer } />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,117 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Button } from '../../../../../common/Button';
|
||||
import { ButtonGroup } from '../../../../../common/ButtonGroup';
|
||||
import { Grid, GridProps } from '../../../../../common/Grid';
|
||||
import { BatchUpdates } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { Offer } from '../../../common/Offer';
|
||||
import { ProductTypeEnum } from '../../../common/ProductTypeEnum';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogGridOfferView } from '../offers/CatalogGridOfferView';
|
||||
|
||||
interface CatalogSpacesWidgetViewProps extends GridProps
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const SPACES_GROUP_NAMES = [ 'floors', 'walls', 'views' ];
|
||||
|
||||
export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =>
|
||||
{
|
||||
const { children = null, ...rest } = props;
|
||||
const [ groupedOffers, setGroupedOffers ] = useState<IPurchasableOffer[][]>(null);
|
||||
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(-1);
|
||||
const [ selectedOfferForGroup, setSelectedOfferForGroup ] = useState<IPurchasableOffer[]>(null);
|
||||
const { currentPage = null, currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null } = useCatalogContext();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentPage) return;
|
||||
|
||||
const groupedOffers: IPurchasableOffer[][] = [ [], [], [] ];
|
||||
|
||||
for(const offer of currentPage.offers)
|
||||
{
|
||||
if((offer.pricingModel !== Offer.PRICING_MODEL_SINGLE) && (offer.pricingModel !== Offer.PRICING_MODEL_MULTI)) continue;
|
||||
|
||||
const product = offer.product;
|
||||
|
||||
if(!product || ((product.productType !== ProductTypeEnum.WALL) && (product.productType !== ProductTypeEnum.FLOOR)) || !product.furnitureData) continue;
|
||||
|
||||
const className = product.furnitureData.className;
|
||||
|
||||
switch(className)
|
||||
{
|
||||
case 'floor':
|
||||
groupedOffers[0].push(offer);
|
||||
break;
|
||||
case 'wallpaper':
|
||||
groupedOffers[1].push(offer);
|
||||
break;
|
||||
case 'landscape':
|
||||
groupedOffers[2].push(offer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setGroupedOffers(groupedOffers);
|
||||
setSelectedGroupIndex(0);
|
||||
setSelectedOfferForGroup([ groupedOffers[0][0], groupedOffers[1][0], groupedOffers[2][0] ]);
|
||||
});
|
||||
}, [ currentPage ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if((selectedGroupIndex === -1) || !selectedOfferForGroup) return;
|
||||
|
||||
setCurrentOffer(selectedOfferForGroup[selectedGroupIndex]);
|
||||
|
||||
}, [ selectedGroupIndex, selectedOfferForGroup, setCurrentOffer ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if((selectedGroupIndex === -1) || !selectedOfferForGroup || !currentOffer) return;
|
||||
|
||||
setPurchaseOptions(prevValue =>
|
||||
{
|
||||
const extraData = selectedOfferForGroup[selectedGroupIndex].product.extraParam;
|
||||
const extraParamRequired = true;
|
||||
|
||||
return { ...prevValue, extraData, extraParamRequired };
|
||||
});
|
||||
}, [ currentOffer, selectedGroupIndex, selectedOfferForGroup, setPurchaseOptions ]);
|
||||
|
||||
if(!groupedOffers || (selectedGroupIndex === -1)) return null;
|
||||
|
||||
const offers = groupedOffers[selectedGroupIndex];
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup>
|
||||
{ SPACES_GROUP_NAMES.map((name, index) => <Button key={ index } active={ (selectedGroupIndex === index) } onClick={ event => setSelectedGroupIndex(index) }>{ LocalizeText(`catalog.spaces.tab.${ name }`) }</Button>) }
|
||||
</ButtonGroup>
|
||||
<Grid grow columnCount={ 5 } overflow="auto" { ...rest }>
|
||||
{ offers && (offers.length > 0) && offers.map((offer, index) =>
|
||||
{
|
||||
const setSelectedOffer = () =>
|
||||
{
|
||||
setSelectedOfferForGroup(prevValue =>
|
||||
{
|
||||
const newValue = [ ...prevValue ];
|
||||
|
||||
newValue[selectedGroupIndex] = offer;
|
||||
|
||||
return newValue;
|
||||
});
|
||||
}
|
||||
|
||||
return <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer === offer)) } offer={ offer } onClick={ setSelectedOffer } />;
|
||||
}) }
|
||||
{ children }
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { Flex } from '../../../../../common/Flex';
|
||||
import { Text } from '../../../../../common/Text';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
const MIN_VALUE: number = 1;
|
||||
const MAX_VALUE: number = 100;
|
||||
|
||||
export const CatalogSpinnerWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const { currentOffer = null, purchaseOptions = null, setPurchaseOptions = null } = useCatalogContext();
|
||||
const { quantity = 1 } = purchaseOptions;
|
||||
|
||||
const updateQuantity = (value: number) =>
|
||||
{
|
||||
value = Math.max(value, MIN_VALUE);
|
||||
value = Math.min(value, MAX_VALUE);
|
||||
|
||||
if(value === quantity) return;
|
||||
|
||||
setPurchaseOptions(prevValue =>
|
||||
{
|
||||
const quantity = value;
|
||||
|
||||
return { ...prevValue, quantity };
|
||||
});
|
||||
}
|
||||
|
||||
if(!currentOffer || !currentOffer.bundlePurchaseAllowed) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text>{ LocalizeText('catalog.bundlewidget.spinner.select.amount') }</Text>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<FontAwesomeIcon icon="caret-left" className="text-black cursor-pointer" onClick={ event => updateQuantity(quantity - 1) } />
|
||||
<input type="number" className="form-control form-control-sm quantity-input" value={ quantity } onChange={ event => updateQuantity(event.target.valueAsNumber)} />
|
||||
<FontAwesomeIcon icon="caret-right" className="text-black cursor-pointer" onClick={ event => updateQuantity(quantity + 1) } />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { FC } from 'react';
|
||||
import { Column, ColumnProps } from '../../../../../common/Column';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
import { CatalogPriceDisplayWidgetView } from './CatalogPriceDisplayWidgetView';
|
||||
|
||||
interface CatalogSimplePriceWidgetViewProps extends ColumnProps
|
||||
{
|
||||
|
||||
}
|
||||
export const CatalogTotalPriceWidget: FC<CatalogSimplePriceWidgetViewProps> = props =>
|
||||
{
|
||||
const { gap = 1, ...rest } = props;
|
||||
const { currentOffer = null } = useCatalogContext();
|
||||
|
||||
return (
|
||||
<Column gap={ gap } { ...rest }>
|
||||
<CatalogPriceDisplayWidgetView offer={ currentOffer } />
|
||||
</Column>
|
||||
);
|
||||
}
|
@ -1,38 +1,106 @@
|
||||
import { IObjectData } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useState } from 'react';
|
||||
import { CatalogSelectProductEvent } from '../../../../../events';
|
||||
import { CatalogSetRoomPreviewerStuffDataEvent } from '../../../../../events/catalog/CatalogSetRoomPreviewerStuffDataEvent';
|
||||
import { CatalogWidgetEvent } from '../../../../../events/catalog/CatalogWidgetEvent';
|
||||
import { BatchUpdates, useUiEvent } from '../../../../../hooks';
|
||||
import { IPurchasableOffer } from '../../../common/IPurchasableOffer';
|
||||
import { Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { GetAvatarRenderManager, GetSessionDataManager } from '../../../../../api';
|
||||
import { Column } from '../../../../../common/Column';
|
||||
import { Grid } from '../../../../../common/Grid';
|
||||
import { LayoutGridItem } from '../../../../../common/layout/LayoutGridItem';
|
||||
import { RoomPreviewerView } from '../../../../../views/shared/room-previewer/RoomPreviewerView';
|
||||
import { FurniCategory } from '../../../common/FurniCategory';
|
||||
import { Offer } from '../../../common/Offer';
|
||||
import { ProductTypeEnum } from '../../../common/ProductTypeEnum';
|
||||
import { useCatalogContext } from '../../../context/CatalogContext';
|
||||
|
||||
export const CatalogViewProductWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const [ selectedProductEvent, setSelectedProductEvent ] = useState<CatalogSelectProductEvent>(null);
|
||||
const [ offer, setOffer ] = useState<IPurchasableOffer>(null);
|
||||
const [ stuffData, setStuffData ] = useState<IObjectData>(null);
|
||||
const { roomPreviewer = null } = useCatalogContext();
|
||||
const { currentOffer = null, roomPreviewer = null, purchaseOptions = null } = useCatalogContext();
|
||||
const { previewStuffData = null } = purchaseOptions;
|
||||
|
||||
const onCatalogSelectProductEvent = useCallback((event: CatalogSelectProductEvent) =>
|
||||
useEffect(() =>
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
if(!currentOffer || (currentOffer.pricingModel === Offer.PRICING_MODEL_BUNDLE) || !roomPreviewer) return;
|
||||
|
||||
const product = currentOffer.product;
|
||||
|
||||
if(!product) return;
|
||||
|
||||
switch(product.productType)
|
||||
{
|
||||
setSelectedProductEvent(event);
|
||||
setOffer(event.offer);
|
||||
})
|
||||
}, []);
|
||||
case ProductTypeEnum.FLOOR: {
|
||||
if(!product.furnitureData) return;
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SELECT_PRODUCT, onCatalogSelectProductEvent);
|
||||
|
||||
const onCatalogSetRoomPreviewerStuffDataEvent = useCallback((event: CatalogSetRoomPreviewerStuffDataEvent) =>
|
||||
if(product.furnitureData.specialType === FurniCategory.FIGURE_PURCHASABLE_SET)
|
||||
{
|
||||
setStuffData(event.stuffData);
|
||||
const furniData = GetSessionDataManager().getFloorItemData(product.furnitureData.id);
|
||||
const customParts = furniData.customParams.split(',').map(value => parseInt(value));
|
||||
const figureSets: number[] = [];
|
||||
|
||||
if(roomPreviewer) roomPreviewer.reset(false);
|
||||
}, [ roomPreviewer ]);
|
||||
for(const part of customParts)
|
||||
{
|
||||
if(GetAvatarRenderManager().isValidFigureSetForGender(part, GetSessionDataManager().gender)) figureSets.push(part);
|
||||
}
|
||||
|
||||
useUiEvent(CatalogWidgetEvent.SET_PREVIEWER_STUFFDATA, onCatalogSetRoomPreviewerStuffDataEvent);
|
||||
const figureString = GetAvatarRenderManager().getFigureStringWithFigureIds(GetSessionDataManager().figure, GetSessionDataManager().gender, figureSets);
|
||||
|
||||
return null;
|
||||
roomPreviewer.addAvatarIntoRoom(figureString, product.productClassId)
|
||||
}
|
||||
else
|
||||
{
|
||||
roomPreviewer.addFurnitureIntoRoom(product.productClassId, new Vector3d(90), previewStuffData, product.extraParam);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ProductTypeEnum.WALL: {
|
||||
if(!product.furnitureData) return;
|
||||
|
||||
switch(product.furnitureData.specialType)
|
||||
{
|
||||
case FurniCategory.FLOOR:
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateObjectRoom(product.extraParam);
|
||||
return;
|
||||
case FurniCategory.WALL_PAPER:
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateObjectRoom(null, product.extraParam);
|
||||
return;
|
||||
case FurniCategory.LANDSCAPE: {
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateObjectRoom(null, null, product.extraParam);
|
||||
|
||||
const furniData = GetSessionDataManager().getWallItemDataByName('ads_twi_windw');
|
||||
|
||||
if(furniData) roomPreviewer.addWallItemIntoRoom(furniData.id, new Vector3d(90), furniData.customParams);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
||||
roomPreviewer.addWallItemIntoRoom(product.productClassId, new Vector3d(90), product.extraParam);
|
||||
return;
|
||||
}
|
||||
}
|
||||
case ProductTypeEnum.ROBOT:
|
||||
roomPreviewer.addAvatarIntoRoom(product.extraParam, 0);
|
||||
return;
|
||||
case ProductTypeEnum.EFFECT:
|
||||
roomPreviewer.addAvatarIntoRoom(GetSessionDataManager().figure, product.productClassId);
|
||||
return;
|
||||
}
|
||||
}, [ currentOffer, previewStuffData, roomPreviewer ]);
|
||||
|
||||
if(!currentOffer) return null;
|
||||
|
||||
if(currentOffer.pricingModel === Offer.PRICING_MODEL_BUNDLE)
|
||||
{
|
||||
return (
|
||||
<Column fit overflow="hidden" className="bg-muted p-2 rounded">
|
||||
<Grid fullWidth grow columnCount={ 4 } overflow="auto" className="nitro-catalog-layout-bundle-grid">
|
||||
{ (currentOffer.products.length > 0) && currentOffer.products.map((product, index) =>
|
||||
{
|
||||
return <LayoutGridItem key={ index } itemImage={ product.getIconUrl(currentOffer) } itemCount={ product.productCount } />;
|
||||
}) }
|
||||
</Grid>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return <RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } />;
|
||||
}
|
||||
|
@ -19,10 +19,31 @@ import { useCatalogContext } from '../../context/CatalogContext';
|
||||
export const CatalogSearchView: FC<{}> = props =>
|
||||
{
|
||||
const [ searchValue, setSearchValue ] = useState('');
|
||||
const [ needsProcessing, setNeedsProcessing ] = useState(false);
|
||||
const { currentType = null, rootNode = null, setActiveNodes = null, offersToNodes = null, searchResult = null, setSearchResult = null, setCurrentPage = null } = useCatalogContext();
|
||||
|
||||
const updateSearchValue = (value: string) =>
|
||||
{
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
if(!value || !value.length)
|
||||
{
|
||||
setSearchValue('');
|
||||
|
||||
if(searchResult) setSearchResult(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
setSearchValue(value);
|
||||
setNeedsProcessing(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const processSearch = useCallback((search: string) =>
|
||||
{
|
||||
setNeedsProcessing(false);
|
||||
|
||||
search = search.toLocaleLowerCase().replace(' ', '');
|
||||
|
||||
if(!search || !search.length) return;
|
||||
@ -62,7 +83,7 @@ export const CatalogSearchView: FC<{}> = props =>
|
||||
{
|
||||
if(searchValues.indexOf(search) >= 0) foundFurniture.push(furniture);
|
||||
|
||||
if(searchValues.length === 250) break;
|
||||
if(foundFurniture.length === 250) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,28 +100,31 @@ export const CatalogSearchView: FC<{}> = props =>
|
||||
{
|
||||
setCurrentPage((new CatalogPage(-1, 'default_3x3', new PageLocalization([], []), offers, false, 1) as ICatalogPage));
|
||||
setSearchResult(new SearchResult(search, offers, nodes.filter(node => (node.isVisible))));
|
||||
setActiveNodes(prevValue => prevValue.slice(0, 1));
|
||||
});
|
||||
}, [ offersToNodes, currentType, rootNode, setCurrentPage, setSearchResult, setActiveNodes ]);
|
||||
}, [ offersToNodes, currentType, rootNode, setCurrentPage, setSearchResult ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!searchValue) return;
|
||||
if(!needsProcessing) return;
|
||||
|
||||
const timeout = setTimeout(() => processSearch(searchValue), 300);
|
||||
|
||||
return () =>
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}, [ searchValue, processSearch ]);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [ searchValue, needsProcessing, processSearch ]);
|
||||
|
||||
return (
|
||||
<Flex gap={ 1 }>
|
||||
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } onKeyDown={ event => (event.code === 'Enter') && processSearch(searchValue) } />
|
||||
<Button variant="primary" size="sm">
|
||||
<Flex fullWidth alignItems="center" position="relative">
|
||||
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => updateSearchValue(event.target.value) } onKeyDown={ event => ((event.code === 'Enter') || (event.code === 'NumpadEnter')) && processSearch(searchValue) } />
|
||||
</Flex>
|
||||
{ (!searchValue || !searchValue.length) &&
|
||||
<Button variant="primary" size="sm" className="catalog-search-button">
|
||||
<FontAwesomeIcon icon="search" />
|
||||
</Button>
|
||||
</Button> }
|
||||
{ searchValue && !!searchValue.length &&
|
||||
<Button variant="primary" size="sm" className="catalog-search-button" onClick={ event => updateSearchValue('') }>
|
||||
<FontAwesomeIcon icon="times" />
|
||||
</Button> }
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
import { Dispatch, FC, SetStateAction } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { Base } from '../../../../common/Base';
|
||||
import { Button } from '../../../../common/Button';
|
||||
import { Flex } from '../../../../common/Flex';
|
||||
import { Text } from '../../../../common/Text';
|
||||
import { useCatalogContext } from '../../context/CatalogContext';
|
||||
|
||||
export interface CatalogSelectGroupViewProps
|
||||
{
|
||||
selectedGroupIndex: number;
|
||||
setSelectedGroupIndex: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const CatalogSelectGroupView: FC<CatalogSelectGroupViewProps> = props =>
|
||||
{
|
||||
const { selectedGroupIndex = -1, setSelectedGroupIndex = null } = props;
|
||||
const { catalogState = null } = useCatalogContext();
|
||||
const { groups = null } = catalogState;
|
||||
|
||||
if(!groups || !groups.length)
|
||||
{
|
||||
return (
|
||||
<Text grow center className="bg-muted rounded p-1">
|
||||
{ LocalizeText('catalog.guild_selector.members_only') }
|
||||
<Button variant="primary" size="sm" className="mt-1">
|
||||
{ LocalizeText('catalog.guild_selector.find_groups') }
|
||||
</Button>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap={ 1 }>
|
||||
<Flex overflow="hidden" className="rounded border">
|
||||
<Base fullHeight style={ { width: '20px', backgroundColor: '#' + groups[selectedGroupIndex].colorA } } />
|
||||
<Base fullHeight style={ { width: '20px', backgroundColor: '#' + groups[selectedGroupIndex].colorB } } />
|
||||
</Flex>
|
||||
<select className="form-select form-select-sm" value={ selectedGroupIndex } onChange={ event => setSelectedGroupIndex(parseInt(event.target.value)) }>
|
||||
{ groups.map((group, index) => <option key={ index } value={ index }>{ group.groupName }</option>) }
|
||||
</select>
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { Dispatch, FC, SetStateAction } from 'react';
|
||||
import { NitroCardTabsItemView } from '../../../../layout';
|
||||
import { ICatalogNode } from '../../common/ICatalogNode';
|
||||
|
||||
interface CatalogTabsViewsProps
|
||||
{
|
||||
node: ICatalogNode;
|
||||
currentTab: ICatalogNode;
|
||||
setCurrentTab: Dispatch<SetStateAction<ICatalogNode>>;
|
||||
}
|
||||
|
||||
export const CatalogTabsViews: FC<CatalogTabsViewsProps> = props =>
|
||||
{
|
||||
const { node = null, currentTab = null, setCurrentTab = null } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ node && (node.children.length > 0) && node.children.map(child =>
|
||||
{
|
||||
if(!child.isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardTabsItemView key={ child.pageId } isActive={ (currentTab === child) } onClick={ event => setCurrentTab(child) }>
|
||||
{ child.localization }
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
}) }
|
||||
</>
|
||||
);
|
||||
}
|
@ -5,8 +5,6 @@ export class CatalogEvent extends NitroEvent
|
||||
public static SHOW_CATALOG: string = 'CE_SHOW_CATALOG';
|
||||
public static HIDE_CATALOG: string = 'CE_HIDE_CATALOG';
|
||||
public static TOGGLE_CATALOG: string = 'CE_TOGGLE_CATALOG';
|
||||
public static PURCHASE_SUCCESS: string = 'CE_PURCHASE_SUCCESS';
|
||||
public static PURCHASE_FAILED: string = 'CE_PURCHASE_FAILED';
|
||||
public static SOLD_OUT: string = 'CE_SOLD_OUT';
|
||||
public static APPROVE_NAME_RESULT: string = 'CE_APPROVE_NAME_RESULT';
|
||||
public static GIFT_RECEIVER_NOT_FOUND: string = 'CE_GIFT_RECEIVER_NOT_FOUND';
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { CatalogEvent } from './CatalogEvent';
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
import { CatalogWidgetEvent } from './CatalogWidgetEvent';
|
||||
|
||||
export class CatalogNameResultEvent extends CatalogEvent
|
||||
export class CatalogNameResultEvent extends NitroEvent
|
||||
{
|
||||
private _result: number;
|
||||
private _validationInfo: string;
|
||||
|
||||
constructor(result: number, validationInfo: string)
|
||||
{
|
||||
super(CatalogEvent.APPROVE_NAME_RESULT);
|
||||
super(CatalogWidgetEvent.APPROVE_RESULT);
|
||||
|
||||
this._result = result;
|
||||
this._validationInfo = validationInfo;
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class CatalogPageOpenedEvent extends NitroEvent
|
||||
{
|
||||
public static CATALOG_PAGE_OPENED: string = 'CPOE_CATALOG_PAGE_OPENED';
|
||||
|
||||
private _pageId: number;
|
||||
private _localization: string;
|
||||
|
||||
constructor(pageId: number, localization: string)
|
||||
{
|
||||
super(CatalogPageOpenedEvent.CATALOG_PAGE_OPENED);
|
||||
|
||||
this._pageId = pageId;
|
||||
this._localization = localization;
|
||||
}
|
||||
|
||||
public get pageId(): number
|
||||
{
|
||||
return this._pageId;
|
||||
}
|
||||
|
||||
public get localization(): string
|
||||
{
|
||||
return this._localization;
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class CatalogPageReadyEvent extends NitroEvent
|
||||
{
|
||||
public static PAGE_READY: string = 'CPRE_PAGE_READY';
|
||||
|
||||
constructor()
|
||||
{
|
||||
super(CatalogPageReadyEvent.PAGE_READY);
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
import { CatalogEvent } from './CatalogEvent';
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class CatalogPurchaseFailureEvent extends CatalogEvent
|
||||
export class CatalogPurchaseFailureEvent extends NitroEvent
|
||||
{
|
||||
public static PURCHASE_FAILED: string = 'CPFE_PURCHASE_FAILED';
|
||||
|
||||
private _code: number;
|
||||
|
||||
constructor(code: number)
|
||||
{
|
||||
super(CatalogEvent.PURCHASE_FAILED);
|
||||
super(CatalogPurchaseFailureEvent.PURCHASE_FAILED);
|
||||
|
||||
this._code = code;
|
||||
}
|
||||
|
20
src/events/catalog/CatalogPurchaseNotAllowedEvent.ts
Normal file
20
src/events/catalog/CatalogPurchaseNotAllowedEvent.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class CatalogPurchaseNotAllowedEvent extends NitroEvent
|
||||
{
|
||||
public static NOT_ALLOWED: string = 'CPNAE_NOT_ALLOWED';
|
||||
|
||||
private _code: number;
|
||||
|
||||
constructor(code: number)
|
||||
{
|
||||
super(CatalogPurchaseNotAllowedEvent.NOT_ALLOWED);
|
||||
|
||||
this._code = code;
|
||||
}
|
||||
|
||||
public get code(): number
|
||||
{
|
||||
return this._code;
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
import { CatalogEvent } from './CatalogEvent';
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class CatalogPurchaseSoldOutEvent extends CatalogEvent
|
||||
export class CatalogPurchaseSoldOutEvent extends NitroEvent
|
||||
{
|
||||
public static SOLD_OUT: string = 'CPSOE_SOLD_OUT';
|
||||
|
||||
constructor()
|
||||
{
|
||||
super(CatalogEvent.SOLD_OUT);
|
||||
super(CatalogPurchaseSoldOutEvent.SOLD_OUT);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { PurchaseOKMessageOfferData } from '@nitrots/nitro-renderer';
|
||||
import { CatalogEvent } from './CatalogEvent';
|
||||
import { NitroEvent, PurchaseOKMessageOfferData } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class CatalogPurchasedEvent extends CatalogEvent
|
||||
export class CatalogPurchasedEvent extends NitroEvent
|
||||
{
|
||||
public static PURCHASE_SUCCESS: string = 'CPE_PURCHASE_SUCCESS';
|
||||
|
||||
private _purchase: PurchaseOKMessageOfferData;
|
||||
|
||||
constructor(purchase: PurchaseOKMessageOfferData)
|
||||
{
|
||||
super(CatalogEvent.PURCHASE_SUCCESS);
|
||||
super(CatalogPurchasedEvent.PURCHASE_SUCCESS);
|
||||
|
||||
this._purchase = purchase;
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
import { IPurchasableOffer } from '../../components/catalog/common/IPurchasableOffer';
|
||||
import { CatalogWidgetEvent } from './CatalogWidgetEvent';
|
||||
|
||||
export class CatalogSelectProductEvent extends NitroEvent
|
||||
{
|
||||
private _offer: IPurchasableOffer;
|
||||
|
||||
constructor(offer: IPurchasableOffer)
|
||||
{
|
||||
super(CatalogWidgetEvent.SELECT_PRODUCT);
|
||||
|
||||
this._offer = offer;
|
||||
}
|
||||
|
||||
public get offer(): IPurchasableOffer
|
||||
{
|
||||
return this._offer;
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
export * from './CatalogEvent';
|
||||
export * from './CatalogNameResultEvent';
|
||||
export * from './CatalogPageOpenedEvent';
|
||||
export * from './CatalogPageReadyEvent';
|
||||
export * from './CatalogPurchasedEvent';
|
||||
export * from './CatalogPurchaseFailureEvent';
|
||||
export * from './CatalogPurchaseNotAllowedEvent';
|
||||
export * from './CatalogPurchaseSoldOutEvent';
|
||||
export * from './CatalogSelectProductEvent';
|
||||
export * from './CatalogSetExtraPurchaseParameterEvent';
|
||||
export * from './SetRoomPreviewerStuffDataEvent';
|
||||
|
Loading…
x
Reference in New Issue
Block a user