feat(hooks): rank-based API tied to permission_ranks DB table

Drop the SecurityLevel-named family (useIsModerator / useIsAdmin /
useIsCommunity / useIsPlayerSupport / useHasSecurityLevel /
useUserSecurityLevel) in favour of a rank-based family tied to the
operator's actual `permission_ranks` rows in the Arcturus DB:

- `useUserRank()` returns `{ id, name, level, badge, prefix,
  prefixColor }` derived from the snapshot. Powered by the renderer's
  extended IUserDataSnapshot (companion commit 87e67d5 on
  feat/react19-event-bus).
- `useHasRankLevel(min)` replaces useHasSecurityLevel; consumers
  pass a `permission_ranks.level` threshold from the deployment.
- `useIsRank(name)` matches `permission_ranks.rank_name` exactly.

To avoid bare integers in widget bodies, added a deployment-scoped
constants file at `src/api/nitro/session/RankLevels.ts`:

  export const STAFF_LEVELS = {
      MEMBER: 1, SUPPORT: 4, MOD: 5, SUPER_MOD: 6, ADMIN: 7
  };

A deployment that re-numbers `permission_ranks` only edits this file.

Migrated all 11 consumer reads (same set as the earlier session's
useIsModerator migration plus the audit catch): ToolbarView,
CatalogClassicView, CatalogModernView, ChooserWidgetView,
CalendarView, YouTubePlayerView, FurniEditorView,
InfoStandWidgetFurniView, AvatarInfoWidgetPetView,
FurnitureMannequinView, NavigatorRoomInfoView. The
NavigatorRoomInfoView `staff_pick` permission was previously
`securityLevel >= COMMUNITY (7)` via the renderer-enum wrapper —
ported to `useHasRankLevel(STAFF_LEVELS.ADMIN)` because in the
default seed level 7 is Administrator, which is the actual rank that
gets the `acc_anyroomowner`-style permissions for staff-picking.

Tests refreshed under `useSessionSnapshots.test.tsx`:
- useUserRank surfaces the full metadata block;
- useHasRankLevel does `>=` against the threshold;
- useIsRank exact-matches against rank_name;
- a runtime promote (snapshot mutation + SESSION_DATA_UPDATED
  dispatch) flips the result, locking in the reactive contract.

Mock extended only minimally — kept the SecurityLevel enum class for
any consumer outside the dropped family that still imports it.

Verification: yarn typecheck clean, yarn lint:hooks clean, yarn test
213/213. The Arcturus-side composer change (UserPermissionsComposer
appending the 5 extra fields) is staged but UNCOMMITTED on Arcturus
main (which has unrelated WIP); the wire is backward-compatible so
the React client works against both pre- and post-extension
emulators.
This commit is contained in:
simoleo89
2026-05-19 18:38:31 +02:00
parent c11a6c4699
commit 8aa02249e1
16 changed files with 192 additions and 106 deletions
@@ -1,10 +1,10 @@
import { CreateLinkEvent, GetCustomRoomFilterMessageComposer, GetGuestRoomMessageComposer, GetSessionDataManager, NavigatorSearchComposer, RemoveOwnRoomRightsRoomMessageComposer, RoomControllerLevel, RoomMuteComposer, RoomSettingsComposer, ToggleStaffPickMessageComposer, UpdateHomeRoomMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import { FaLink, FaSignOutAlt } from 'react-icons/fa';
import { DispatchUiEvent, GetGroupInformation, LocalizeText, ReportType, SendMessageComposer, ToggleFavoriteRoom } from '../../../api';
import { DispatchUiEvent, GetGroupInformation, LocalizeText, ReportType, SendMessageComposer, STAFF_LEVELS, ToggleFavoriteRoom } from '../../../api';
import { Button, Column, Flex, LayoutBadgeImageView, LayoutRoomThumbnailView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, UserProfileIconView } from '../../../common';
import { RoomWidgetThumbnailEvent } from '../../../events';
import { useHelp, useIsCommunity, useIsModerator, useNavigator, useRoom } from '../../../hooks';
import { useHasRankLevel, useHelp, useNavigator, useRoom } from '../../../hooks';
import { classNames } from '../../../layout';
export interface NavigatorRoomInfoViewProps {
@@ -19,8 +19,8 @@ export const NavigatorRoomInfoView: FC<NavigatorRoomInfoViewProps> = props =>
const { report = null } = useHelp();
const { navigatorData = null, favouriteRoomIds = [] } = useNavigator();
const { roomSession = null } = useRoom();
const isModerator = useIsModerator();
const isCommunity = useIsCommunity();
const isModerator = useHasRankLevel(STAFF_LEVELS.MOD);
const isAdmin = useHasRankLevel(STAFF_LEVELS.ADMIN);
const enteredRoomId = navigatorData?.enteredGuestRoom?.roomId ?? 0;
@@ -55,7 +55,7 @@ export const NavigatorRoomInfoView: FC<NavigatorRoomInfoViewProps> = props =>
case 'settings':
return (GetSessionDataManager().userId === navigatorData.enteredGuestRoom.ownerId || isModerator);
case 'staff_pick':
return isCommunity;
return isAdmin;
case 'floor':
return roomSession?.controllerLevel >= RoomControllerLevel.GUEST;
case 'guest':