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
+3 -2
View File
@@ -1,7 +1,7 @@
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react';
import { CalendarItemState, ICalendarItem, LocalizeText } from '../../api';
import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { useIsModerator } from '../../hooks';
import { CalendarItemView } from './CalendarItemView';
interface CalendarViewProps
@@ -23,6 +23,7 @@ export const CalendarView: FC<CalendarViewProps> = props =>
const { onClose = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props;
const [ selectedDay, setSelectedDay ] = useState(currentDay);
const [ index, setIndex ] = useState(Math.max(0, (selectedDay - 1)));
const isModerator = useIsModerator();
const getDayState = (day: number) =>
{
@@ -109,7 +110,7 @@ export const CalendarView: FC<CalendarViewProps> = props =>
<Text>{ dayMessage(selectedDay) }</Text>
</div>
<div>
{ GetSessionDataManager().isModerator &&
{ isModerator &&
<Button variant="danger" onClick={ forceOpen }>Force open</Button> }
</div>
</div>