mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +00:00
useClubOffers: per-windowId TanStack query for HC offer pages
Two catalog layouts each fire 'new GetClubOffersMessageComposer(windowId)'
on mount and read parser.offers via HabboClubOffersMessageEvent:
- CatalogLayoutVipBuyView (windowId 1)
- CatalogLayoutBuildersClubBuyView (windowId 2 / 3, depending on
the addon variant)
Plus useCatalog used to also listen for HabboClubOffersMessageEvent and
stash the offers in 'catalogOptions.clubOffersByWindowId[windowId]' and
'catalogOptions.clubOffers' (the latter being a backward-compat alias
for windowId 1). Three listeners, three independent requests when all
mounted.
New useClubOffers(windowId) wraps the request/response pair as a
TanStack query keyed by '['nitro', 'catalog', 'clubOffers', windowId]'.
accept(): correlation-key filter (parser.windowId === windowId) so
the same multiplexed event doesn't satisfy the wrong query slot.
Both layouts now read 'const { data: offers = null } = useClubOffers(windowId)';
useCatalog drops the listener, ICatalogOptions drops the
clubOffers / clubOffersByWindowId fields and HabboClubOffersMessageEvent
no longer needs to be imported in useCatalog. The localization-refresh
effect that re-cloned both fields is also dropped — React Query owns
the cache now, and ClubOfferData has no localized strings anyway.
This commit is contained in:
@@ -1,11 +1,9 @@
|
|||||||
import { ClubGiftInfoParser, ClubOfferData, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer';
|
import { ClubGiftInfoParser, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer';
|
||||||
import { CatalogPetPalette } from './CatalogPetPalette';
|
import { CatalogPetPalette } from './CatalogPetPalette';
|
||||||
|
|
||||||
export interface ICatalogOptions
|
export interface ICatalogOptions
|
||||||
{
|
{
|
||||||
petPalettes?: CatalogPetPalette[];
|
petPalettes?: CatalogPetPalette[];
|
||||||
clubOffers?: ClubOfferData[];
|
|
||||||
clubOffersByWindowId?: Record<number, ClubOfferData[]>;
|
|
||||||
clubGifts?: ClubGiftInfoParser;
|
clubGifts?: ClubGiftInfoParser;
|
||||||
marketplaceConfiguration?: MarketplaceConfigurationMessageParser;
|
marketplaceConfiguration?: MarketplaceConfigurationMessageParser;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api';
|
import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api';
|
||||||
import { Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
|
import { Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
|
||||||
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
|
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 { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
|
||||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||||
|
|
||||||
@@ -14,12 +14,12 @@ export const CatalogLayoutBuildersClubBuyView: FC<CatalogLayoutProps> = () =>
|
|||||||
{
|
{
|
||||||
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
|
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
|
||||||
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
|
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
|
||||||
const { currentPage = null, catalogOptions = null } = useCatalog();
|
const { currentPage = null } = useCatalog();
|
||||||
const { getCurrencyAmount = null } = usePurse();
|
const { getCurrencyAmount = null } = usePurse();
|
||||||
const isPurchasingRef = useRef(false);
|
const isPurchasingRef = useRef(false);
|
||||||
const isAddonLayout = (currentPage?.layoutCode === 'builders_club_addons');
|
const isAddonLayout = (currentPage?.layoutCode === 'builders_club_addons');
|
||||||
const windowId = (isAddonLayout ? BUILDERS_CLUB_ADDONS_WINDOW_ID : BUILDERS_CLUB_WINDOW_ID);
|
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) =>
|
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||||
{
|
{
|
||||||
@@ -120,11 +120,6 @@ export const CatalogLayoutBuildersClubBuyView: FC<CatalogLayoutProps> = () =>
|
|||||||
return currentPage.localization.getText(1) || currentPage.localization.getText(2) || currentPage.localization.getText(0) || '';
|
return currentPage.localization.getText(1) || currentPage.localization.getText(2) || currentPage.localization.getText(0) || '';
|
||||||
}, [ currentPage ]);
|
}, [ currentPage ]);
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(!offers) SendMessageComposer(new GetClubOffersMessageComposer(windowId));
|
|
||||||
}, [ offers, windowId ]);
|
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!offers || !offers.length) return;
|
if(!offers || !offers.length) return;
|
||||||
|
|||||||
@@ -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 { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api';
|
import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api';
|
||||||
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
|
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
|
||||||
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
|
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
|
||||||
import { useCatalog, usePurse, useUiEvent } from '../../../../../hooks';
|
import { useCatalog, useClubOffers, usePurse, useUiEvent } from '../../../../../hooks';
|
||||||
import { CatalogLayoutProps } from './CatalogLayout.types';
|
import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||||
|
|
||||||
|
const VIP_WINDOW_ID = 1;
|
||||||
|
|
||||||
export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
|
export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
|
||||||
{
|
{
|
||||||
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
|
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
|
||||||
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
|
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
|
||||||
const { currentPage = null, catalogOptions = null } = useCatalog();
|
const { currentPage = null } = useCatalog();
|
||||||
const { purse = null, getCurrencyAmount = null } = usePurse();
|
const { purse = null, getCurrencyAmount = null } = usePurse();
|
||||||
const { clubOffers = null, clubOffersByWindowId = null } = (catalogOptions || {});
|
const { data: offers = null } = useClubOffers(VIP_WINDOW_ID);
|
||||||
const offers = clubOffersByWindowId?.[1] || clubOffers;
|
|
||||||
const isPurchasingRef = useRef<boolean>(false);
|
const isPurchasingRef = useRef<boolean>(false);
|
||||||
|
|
||||||
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
const onCatalogEvent = useCallback((event: CatalogEvent) =>
|
||||||
@@ -128,11 +129,6 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
|
|||||||
}
|
}
|
||||||
}, [ pendingOffer, purchaseState, purchaseSubscription, getCurrencyAmount ]);
|
}, [ pendingOffer, purchaseState, purchaseSubscription, getCurrencyAmount ]);
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(!offers) SendMessageComposer(new GetClubOffersMessageComposer(1));
|
|
||||||
}, [ offers ]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
<Column fullHeight justifyContent="between" overflow="hidden" size={ 7 }>
|
<Column fullHeight justifyContent="between" overflow="hidden" size={ 7 }>
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export * from './useCatalog';
|
|||||||
export * from './useCatalogFavorites';
|
export * from './useCatalogFavorites';
|
||||||
export * from './useCatalogPlaceMultipleItems';
|
export * from './useCatalogPlaceMultipleItems';
|
||||||
export * from './useCatalogSkipPurchaseConfirmation';
|
export * from './useCatalogSkipPurchaseConfirmation';
|
||||||
|
export * from './useClubOffers';
|
||||||
export * from './useGiftConfiguration';
|
export * from './useGiftConfiguration';
|
||||||
|
|||||||
@@ -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 { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useBetween } from 'use-between';
|
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';
|
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>(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>(MarketplaceMakeOfferResult, event =>
|
useMessageEvent<MarketplaceMakeOfferResult>(MarketplaceMakeOfferResult, event =>
|
||||||
@@ -1045,23 +1029,6 @@ const useCatalogState = () =>
|
|||||||
|
|
||||||
return new CatalogPage(prevValue.pageId, prevValue.layoutCode, prevValue.localization, offers, prevValue.acceptSeasonCurrencyAsCredits, prevValue.mode);
|
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);
|
window.addEventListener('nitro-localization-updated', refreshCatalogLocalization);
|
||||||
|
|||||||
@@ -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<ClubOfferData[]> =>
|
||||||
|
useNitroQuery<HabboClubOffersMessageEvent, ClubOfferData[]>({
|
||||||
|
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
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user