diff --git a/src/api/catalog/ICatalogOptions.ts b/src/api/catalog/ICatalogOptions.ts index a666d44..4842d97 100644 --- a/src/api/catalog/ICatalogOptions.ts +++ b/src/api/catalog/ICatalogOptions.ts @@ -1,11 +1,9 @@ -import { ClubGiftInfoParser, ClubOfferData, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer'; +import { ClubGiftInfoParser, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer'; import { CatalogPetPalette } from './CatalogPetPalette'; export interface ICatalogOptions { petPalettes?: CatalogPetPalette[]; - clubOffers?: ClubOfferData[]; - clubOffersByWindowId?: Record; clubGifts?: ClubGiftInfoParser; marketplaceConfiguration?: MarketplaceConfigurationMessageParser; } diff --git a/src/components/catalog/views/page/layout/CatalogLayoutBuildersClubBuyView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutBuildersClubBuyView.tsx index 0ea8069..19e8c2c 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutBuildersClubBuyView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutBuildersClubBuyView.tsx @@ -1,9 +1,9 @@ -import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; +import { ClubOfferData, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api'; import { Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common'; import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events'; -import { useCatalog, usePurse, useUiEvent } from '../../../../../hooks'; +import { useCatalog, useClubOffers, usePurse, useUiEvent } from '../../../../../hooks'; import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView'; import { CatalogLayoutProps } from './CatalogLayout.types'; @@ -14,12 +14,12 @@ export const CatalogLayoutBuildersClubBuyView: FC = () => { const [ pendingOffer, setPendingOffer ] = useState(null); const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE); - const { currentPage = null, catalogOptions = null } = useCatalog(); + const { currentPage = null } = useCatalog(); const { getCurrencyAmount = null } = usePurse(); const isPurchasingRef = useRef(false); const isAddonLayout = (currentPage?.layoutCode === 'builders_club_addons'); const windowId = (isAddonLayout ? BUILDERS_CLUB_ADDONS_WINDOW_ID : BUILDERS_CLUB_WINDOW_ID); - const offers = catalogOptions?.clubOffersByWindowId?.[windowId] || null; + const { data: offers = null } = useClubOffers(windowId); const onCatalogEvent = useCallback((event: CatalogEvent) => { @@ -120,11 +120,6 @@ export const CatalogLayoutBuildersClubBuyView: FC = () => return currentPage.localization.getText(1) || currentPage.localization.getText(2) || currentPage.localization.getText(0) || ''; }, [ currentPage ]); - useEffect(() => - { - if(!offers) SendMessageComposer(new GetClubOffersMessageComposer(windowId)); - }, [ offers, windowId ]); - useEffect(() => { if(!offers || !offers.length) return; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx index c7b5430..b63f4f6 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx @@ -1,19 +1,20 @@ -import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; +import { ClubOfferData, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api'; import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common'; import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events'; -import { useCatalog, usePurse, useUiEvent } from '../../../../../hooks'; +import { useCatalog, useClubOffers, usePurse, useUiEvent } from '../../../../../hooks'; import { CatalogLayoutProps } from './CatalogLayout.types'; +const VIP_WINDOW_ID = 1; + export const CatalogLayoutVipBuyView: FC = props => { const [ pendingOffer, setPendingOffer ] = useState(null); const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE); - const { currentPage = null, catalogOptions = null } = useCatalog(); + const { currentPage = null } = useCatalog(); const { purse = null, getCurrencyAmount = null } = usePurse(); - const { clubOffers = null, clubOffersByWindowId = null } = (catalogOptions || {}); - const offers = clubOffersByWindowId?.[1] || clubOffers; + const { data: offers = null } = useClubOffers(VIP_WINDOW_ID); const isPurchasingRef = useRef(false); const onCatalogEvent = useCallback((event: CatalogEvent) => @@ -128,11 +129,6 @@ export const CatalogLayoutVipBuyView: FC = props => } }, [ pendingOffer, purchaseState, purchaseSubscription, getCurrencyAmount ]); - useEffect(() => - { - if(!offers) SendMessageComposer(new GetClubOffersMessageComposer(1)); - }, [ offers ]); - return ( diff --git a/src/hooks/catalog/index.ts b/src/hooks/catalog/index.ts index da4144f..288ef99 100644 --- a/src/hooks/catalog/index.ts +++ b/src/hooks/catalog/index.ts @@ -2,4 +2,5 @@ export * from './useCatalog'; export * from './useCatalogFavorites'; export * from './useCatalogPlaceMultipleItems'; export * from './useCatalogSkipPurchaseConfirmation'; +export * from './useClubOffers'; export * from './useGiftConfiguration'; diff --git a/src/hooks/catalog/useCatalog.ts b/src/hooks/catalog/useCatalog.ts index cbd9cff..a68c36e 100644 --- a/src/hooks/catalog/useCatalog.ts +++ b/src/hooks/catalog/useCatalog.ts @@ -1,4 +1,4 @@ -import { BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPublishedMessageEvent, ClubGiftInfoEvent, CreateLinkEvent, FrontPageItem, FurniturePlaceComposer, FurniturePlacePaintComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetRoomEngine, GetSessionDataManager, GetTickerTime, HabboClubOffersMessageEvent, LegacyDataType, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, RoomControllerLevel, RoomEngineObjectPlacedEvent, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType, RoomObjectVariable, RoomPreviewer, SellablePetPalettesMessageEvent, Vector3d } from '@nitrots/nitro-renderer'; +import { BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPublishedMessageEvent, ClubGiftInfoEvent, CreateLinkEvent, FrontPageItem, FurniturePlaceComposer, FurniturePlacePaintComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetRoomEngine, GetSessionDataManager, GetTickerTime, LegacyDataType, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, RoomControllerLevel, RoomEngineObjectPlacedEvent, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType, RoomObjectVariable, RoomPreviewer, SellablePetPalettesMessageEvent, Vector3d } from '@nitrots/nitro-renderer'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useBetween } from 'use-between'; import { BuilderFurniPlaceableStatus, CatalogNode, CatalogPage, CatalogPetPalette, CatalogType, DispatchUiEvent, FurniCategory, GetFurnitureData, GetProductDataForLocalization, GetRoomSession, ICatalogNode, ICatalogOptions, ICatalogPage, IPageLocalization, IProduct, IPurchasableOffer, IPurchaseOptions, LocalizeText, NotificationAlertType, Offer, PageLocalization, PlacedObjectPurchaseData, PlaySound, Product, ProductTypeEnum, RequestedPage, SearchResult, SendMessageComposer, SoundNames } from '../../api'; @@ -739,22 +739,6 @@ const useCatalogState = () => }); }); - useMessageEvent(HabboClubOffersMessageEvent, event => - { - const parser = event.getParser(); - - setCatalogOptions(prevValue => - { - const windowId = parser.windowId; - const clubOffersByWindowId = { ...(prevValue.clubOffersByWindowId || {}) }; - - clubOffersByWindowId[windowId] = parser.offers; - - const clubOffers = clubOffersByWindowId[1] || prevValue.clubOffers; - - return { ...prevValue, clubOffers, clubOffersByWindowId }; - }); - }); useMessageEvent(MarketplaceMakeOfferResult, event => @@ -1045,23 +1029,6 @@ const useCatalogState = () => return new CatalogPage(prevValue.pageId, prevValue.layoutCode, prevValue.localization, offers, prevValue.acceptSeasonCurrencyAsCredits, prevValue.mode); }); - setCatalogOptions(prevValue => - { - if(!prevValue) return prevValue; - - const clubOffersByWindowId = { ...(prevValue.clubOffersByWindowId || {}) }; - - Object.keys(clubOffersByWindowId).forEach(key => - { - const offers = clubOffersByWindowId[key]; - - if(Array.isArray(offers)) clubOffersByWindowId[key] = [ ...offers ]; - }); - - const clubOffers = Array.isArray(prevValue.clubOffers) ? [ ...prevValue.clubOffers ] : prevValue.clubOffers; - - return { ...prevValue, clubOffers, clubOffersByWindowId }; - }); }; window.addEventListener('nitro-localization-updated', refreshCatalogLocalization); diff --git a/src/hooks/catalog/useClubOffers.ts b/src/hooks/catalog/useClubOffers.ts new file mode 100644 index 0000000..964f6d4 --- /dev/null +++ b/src/hooks/catalog/useClubOffers.ts @@ -0,0 +1,33 @@ +import { ClubOfferData, GetClubOffersMessageComposer, HabboClubOffersMessageEvent } from '@nitrots/nitro-renderer'; +import { UseQueryResult } from '@tanstack/react-query'; +import { useNitroQuery } from '../../api/nitro-query'; + +/** + * Habbo Club offer list keyed by Catalog `windowId`. windowId 1 is the + * VIP buy page; 2 / 3 are the Builders Club / Builders Club Addons + * pages. Each catalog layout asks the server for its own slice via + * GetClubOffersMessageComposer(windowId) — the server replies with a + * HabboClubOffersMessageEvent carrying parser.windowId + parser.offers. + * + * Wrapped as a TanStack query so multiple consumers reading the same + * windowId share one request, and reopening the page within the + * session-stable cache window doesn't re-fetch. + * + * The accept() predicate filters out responses tagged with a different + * windowId — the renderer multiplexes the same event for every page, + * so without the filter a slow VIP response would land in a Builders + * Club query. + */ +export const useClubOffers = ( + windowId: number, + options: { enabled?: boolean } = {} +): UseQueryResult => + useNitroQuery({ + key: [ 'nitro', 'catalog', 'clubOffers', windowId ], + request: () => new GetClubOffersMessageComposer(windowId), + parser: HabboClubOffersMessageEvent, + accept: event => (event.getParser().windowId === windowId), + select: event => (event.getParser().offers || []), + enabled: options.enabled, + staleTime: Infinity + });