useClubGifts + useNitroEventInvalidator: close the catalogOptions bag

This commit drains the last field out of ICatalogOptions (clubGifts)
and deletes the interface — useCatalog no longer owns a catch-all
mutable object that downstream components stuff data into.

Two pieces:

1) New useNitroEventInvalidator(eventType, queryKey, accept?) — a
   small companion to useNitroQuery for the case where the server
   pushes the same event unprompted (e.g. ClubGiftInfoEvent fires
   both as the response to GetClubGiftInfo and again after the user
   claims a gift via SelectClubGiftComposer). It calls
   queryClient.invalidateQueries() on each matching push so the
   next render of any subscriber triggers a fresh queryFn.

2) New useClubGifts() — useNitroQuery on the ClubGiftInfoEvent
   pair, paired with useNitroEventInvalidator so server-driven
   pushes refresh the cache automatically. CatalogLayoutVipGiftsView
   now consumes the query directly. The local optimistic
   'giftsAvailable--' mutation (which side-effected the parser
   object passed back to the catalog state!) is dropped — the
   server's authoritative ClubGiftInfoEvent push is the single
   source of truth via the invalidator.

useCatalog drops the matching listener + the GetClubGiftInfo dispatch
from the catalog-open effect. ICatalogOptions is now empty and
deleted; the catalogOptions / setCatalogOptions state + return-shape
field are removed from useCatalog along with the import.
This commit is contained in:
simoleo89
2026-05-11 22:38:32 +02:00
parent 9a807bf335
commit 7b062299de
8 changed files with 106 additions and 42 deletions
@@ -2,7 +2,7 @@ import { SelectClubGiftComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../../../../api';
import { AutoGrid, Text } from '../../../../../../common';
import { useCatalog, useNotification, usePurse } from '../../../../../../hooks';
import { useClubGifts, useNotification, usePurse } from '../../../../../../hooks';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { VipGiftItem } from './VipGiftItemView';
@@ -11,8 +11,7 @@ let isSelectingGift = false;
export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
{
const { purse = null } = usePurse();
const { catalogOptions = null, setCatalogOptions = null } = useCatalog();
const { clubGifts = null } = catalogOptions;
const { data: clubGifts = null } = useClubGifts();
const { showConfirm = null } = useNotification();
const giftsAvailable = useCallback(() =>
@@ -36,34 +35,32 @@ export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
isSelectingGift = true;
// The server replies with a fresh ClubGiftInfoEvent after
// accepting the selection; useClubGifts subscribes to that
// event via useNitroEventInvalidator, so giftsAvailable
// refreshes from the authoritative source — no need to
// mutate the parser locally.
SendMessageComposer(new SelectClubGiftComposer(localizationId));
setCatalogOptions(prevValue =>
{
prevValue.clubGifts.giftsAvailable--;
return { ...prevValue };
});
setTimeout(() => isSelectingGift = false, 5000);
}, null);
}, [ setCatalogOptions, showConfirm ]);
}, [ showConfirm ]);
const sortGifts = useMemo(() =>
{
let gifts = clubGifts.offers.sort((a,b) =>
{
return clubGifts.getOfferExtraData(a.offerId).daysRequired - clubGifts.getOfferExtraData(b.offerId).daysRequired;
});
return gifts;
},[ clubGifts ]);
if(!clubGifts) return [];
return [ ...clubGifts.offers ].sort((a, b) =>
(clubGifts.getOfferExtraData(a.offerId).daysRequired - clubGifts.getOfferExtraData(b.offerId).daysRequired)
);
}, [ clubGifts ]);
return (
<>
<Text shrink truncate fontWeight="bold">{ giftsAvailable() }</Text>
<AutoGrid className="nitro-catalog-layout-vip-gifts-grid" columnCount={ 1 }>
{ (clubGifts.offers.length > 0) && sortGifts.map(offer => <VipGiftItem key={ offer.offerId } daysRequired={ clubGifts.getOfferExtraData(offer.offerId).daysRequired } isAvailable={ (clubGifts.getOfferExtraData(offer.offerId).isSelectable && (clubGifts.giftsAvailable > 0)) } offer={ offer } onSelect={ selectGift }/>) }
{ clubGifts && (clubGifts.offers.length > 0) && sortGifts.map(offer => <VipGiftItem key={ offer.offerId } daysRequired={ clubGifts.getOfferExtraData(offer.offerId).daysRequired } isAvailable={ (clubGifts.getOfferExtraData(offer.offerId).isSelectable && (clubGifts.giftsAvailable > 0)) } offer={ offer } onSelect={ selectGift }/>) }
</AutoGrid>
</>
);