feat(hooks): useIsModerator() + migrate 6 component reads

Adds a reactive `useIsModerator()` derived from
`useUserDataSnapshot().securityLevel >= SecurityLevel.MODERATOR`
(mirrors the renderer-side getter at SessionDataManager.ts:684), and
migrates the six React component-body reads of
`GetSessionDataManager().isModerator`:

- ToolbarView (mod-only chat-input button)
- CatalogClassicView, CatalogModernView (admin toggles in catalog
  header)
- ChooserWidgetView (room-object id column visibility)
- YouTubePlayerView (room-control affordance — hook moved above the
  `if (!isOpen) return null` early return so the hook order stays
  stable when the player opens/closes)
- CalendarView (mod-only "open all" affordance)

UX impact: any future promote/demote that flips
SESSION_DATA_UPDATED now re-renders the mod-only UI live, instead of
requiring an F5. Imperative call sites
(AvatarInfoUtilities.populate*, CanManipulateFurniture,
RoomChatHandler) still read the manager directly — they run at click
time, not in a React render, so reactivity has no upside there.

Five of the six call sites are top-level component-body reads (no
early-return interaction). YouTubePlayerView has an
`if (!isOpen) return null` below the hook list, so the hook had to
move ABOVE it; same shape as the recent CatalogPurchaseWidgetView and
CatalogItemGridWidgetView fixes.

Verification: yarn typecheck clean, yarn lint:hooks clean, yarn test
209/209.
This commit is contained in:
simoleo89
2026-05-19 18:07:17 +02:00
parent 3459400ed7
commit 532cb28ca7
7 changed files with 36 additions and 17 deletions
@@ -1,7 +1,8 @@
import { FurniturePickupAllComposer, GetSessionDataManager } from '@nitrots/nitro-renderer';
import { FurniturePickupAllComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useEffectEvent, useMemo, useState } from 'react';
import { LocalizeText, RoomObjectItem, SendMessageComposer, chooserSelectionVisualizer } from '../../../../api';
import { Button, Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { useIsModerator } from '../../../../hooks';
import { NitroInput, classNames } from '../../../../layout';
const LIMIT_FURNI_PICKALL = 100;
@@ -23,7 +24,7 @@ export const ChooserWidgetView: FC<ChooserWidgetViewProps> = props =>
const [ searchValue, setSearchValue ] = useState('');
const [ checkAll, setCheckAll ] = useState(false);
const [ checkedIds, setCheckedIds ] = useState<number[]>([]);
const canSeeId = GetSessionDataManager().isModerator;
const canSeeId = useIsModerator();
const ownerNames = useMemo(() =>
{