Commit Graph

18 Commits

Author SHA1 Message Date
simoleo89 0ad284fa9c refactor(mod-tools): drop the launcher Context strip
The Context strip at the top of the launcher showed which room the
mod is currently observing — green pill + door icon when in a room,
zinc strip when not. In practice it's noise: the Room Tool / Chatlog
Tool buttons right under it already gate on the same in-room state
(disabled when not in a room) and carry their own tooltip explaining
that. The strip duplicated the signal without adding actionable info.

Remove the section, the now-unused FaDoorOpen / FaDoorClosed imports,
and the matching `modtools.window.section.context` /
`modtools.window.context.room` locale keys (from both the runtime
UITexts.json and the versioned UITexts.example template).
2026-05-20 22:01:58 +02:00
simoleo89 46daa96100 fix(mod-tools): empty-value placeholder no longer renders as music note
User reported empty fields (Email, Last Purchase, Lock Expires, Banned
Accs, Abusive CFHs) showing what looks like a music-note glyph next to
the label. They aren't censored — they're genuinely empty (a rank-7
Administrator account has none of that data populated). The em-dash
"—" (U+2014) used as the placeholder doesn't have a glyph in the
Habbo pixel font (Volter / Volter-Goldfish), so the engine falls
through to a placeholder glyph that on some font stacks looks like a
music note.

Two-part fix in ModToolsUserView (Field), ModToolsIssueInfoView
(Field) and ModToolsRoomView (owner fallback):

1. Replace the U+2014 em-dash with a plain ASCII `-`. Hyphen-minus is
   safely in Volter, so the placeholder renders correctly across the
   whole client.

2. The `value || placeholder` guard is now `(value || value === 0)`.
   Stat fields whose value is the literal number 0 — a clean account
   with cfhCount=0, banCount=0, cautionCount=0 — were rendering the
   placeholder because 0 is falsy. Treat 0 as a real value.

Also dropped the `italic` class on the placeholder span — the
hyphen does the job on its own and italic on a single-character
glyph in a pixel font was making it look like a tilted line.
2026-05-20 21:41:52 +02:00
simoleo89 91938985a2 refactor(mod-tools): launcher box gets context strip + section grouping
The launcher panel was a flat stack of four buttons (Room Tool, Chatlog
Tool, selected-user + presence dot inline, Report Tool) with no visual
hierarchy. The selected-user row was particularly cramped — name, the
2px dot and the 4×4 close-X all crammed into a single button row, easy
to misclick.

Reorganize into four logical groups, each with a small uppercase
section label:

  Context  — gradient strip (emerald when in a room, zinc when not)
             showing "Room #<id>" or "Enter a room first" with a
             matching door icon. Source of truth for "what is the
             mod observing right now"; both Room Tool and Chatlog
             Tool feed from the same currentRoomId.

  Room     — Room Tool + Chatlog Tool stacked. Both still gate on
             isInRoom; the disabled state now reads from a single
             flag instead of repeating `currentRoomId <= 0`.

  User     — When a user is selected: a card with the presence dot
             (emerald = still in room, zinc = left), the username at
             a real legible size, a bigger close button, plus a
             dedicated "Open Info" button to toggle ModToolsUserView.
             Splitting the click target from the close action removes
             the misclick footgun.
             When no user is selected: a dashed-border empty state
             with a FaUserSlash icon and the "Select a user" hint —
             reads as a clear "no selection" instead of an active
             button you can't press.

  Reports  — Report Tool with the open-ticket badge. Badge gets a 2px
             rose halo box-shadow so a new ticket pulses into view
             instead of competing with the button background.

Locale keys added under modtools.window.section.* and
modtools.window.context.room / modtools.window.user.open_info, in both
the runtime UITexts.json and the versioned UITexts.example template.

The "Open Info" button label is a fix in flight — the old layout
overloaded the username row to also open user info, with no separate
label. The new explicit button gets its own key so the action is
unambiguous (the previous version mislabelled the button as "Mod
Action", which is actually a different sub-panel).

typecheck + vitest 214/214 + JSON validation all clean.
2026-05-20 21:41:52 +02:00
simoleo89 a9515cb1a0 fix(mod-tools): chatlog wrappers back to useMessageEvent + useEffect
ModToolsChatlogView and CfhChatlogView were on the useNitroQuery
pattern. Symptom: the card opens, the spinner spins, but the data
never arrives — even when the server is correctly answering with
ModToolRoomChatlogComposer (header 3434) and GetCfhChatlogComposer
(607). Both header IDs match the renderer's Incoming map, both server
handlers gate only on ACC_SUPPORTTOOL and reply unconditionally when
the room/issue lookup succeeds. So the request DOES go out and the
response DOES come back — but useNitroQuery's listener (registered
via `new (ParserCtor)(callback)` + `registerMessageEvent`) isn't
delivering the event to the React side here.

ModToolsUserChatlogView already uses the plain `useMessageEvent` +
`useEffect(sendComposer)` pattern and works on this same setup, so
align the two broken views with it. Keep the loading-spinner empty
state introduced yesterday so the user still gets visible feedback
while the response is in flight.

This sidesteps useNitroQuery for these two cases rather than fixing
it in place — the underlying createNitroQuery + listener registration
plumbing still works for OfferView, useUserGroups, useClubOffers,
useSellablePetPalette, useMarketplaceConfiguration, useClubGifts,
CatalogLayoutRoomAdsView, so the regression is specific to these two
parsers and worth investigating separately. Filed as a follow-up.
2026-05-20 21:41:52 +02:00
simoleo89 65af9a564d fix(mod-tools): Room Chatlog button now renders a loading state
ModToolsChatlogView returned null whenever roomChatlog was undefined
— including the entire window between click and server response (up
to a 15-second NitroQuery timeout). Result: clicking the Chatlog
button in the launcher or in Room Info appeared to do nothing at all
on any session where the server reply was slow or the accept-filter
correlation didn't match.

The other two chatlog wrappers (ModToolsUserChatlogView,
CfhChatlogView) already render a spinner while data is loading after
yesterday's redesign — this view was the one I missed.

Apply the same fix: always render the NitroCardView, and show the
FaSpinner loading state inside until useNitroQuery resolves.
2026-05-20 21:41:52 +02:00
simoleo89 75815fa022 i18n(mod-tools): route every label/title/placeholder through LocalizeText
The ModTools template refresh introduced ~80 hardcoded English strings
(labels, placeholders, tooltips, empty-state copy, button text). Move
every one of them onto the modtools.* namespace and read via
LocalizeText so the panels translate alongside the rest of the client.

UITexts.example (versioned template) extended with the full set:

  modtools.window.*            Launcher box (toolbar item, tools,
                               selected-user state, ticket count)
  modtools.userinfo.*          User info card — already had the
                               legacy modtools.userinfo.{userName,
                               cfhCount, …} keys from before; added
                               refresh tooltip, presence pill labels
                               (in_room / online / offline with
                               matching .title tooltips), section
                               headings, action button labels, stat
                               card labels
  modtools.roominfo.*          Room info card — title, refresh, loading,
                               owner pill (here/away + tooltips), stat
                               labels, action buttons, moderate panel
                               heading + checkboxes + textarea
                               placeholder + caution/alert CTAs
  modtools.user.message.*      Send-message dialog (recipient label,
                               body label, placeholder, char counter,
                               empty state, send button)
  modtools.user.modaction.*    Mod Action form — header, sanctioning
                               label, 3-step section titles, select
                               placeholders, message label + optional
                               note, message placeholder, preview
                               heading, default/apply buttons, every
                               sendAlert error message
  modtools.user.visits.*       Room visits — title, header strip
                               heading, entry count (singular/plural),
                               empty state, column headers, visit
                               button + tooltip
  modtools.user.chatlog.*      User chatlog — title (with username
                               variant), loading state
  modtools.room.chatlog.*      Room chatlog title
  modtools.chatlog.*           Shared ChatlogView — column headers,
                               empty state, room-separator Visit/Tools
                               buttons
  modtools.tickets.*           Tickets window — title, tab labels
                               (open/mine/picked), column headers,
                               empty states, action buttons (pick/
                               handle/release), issue resolution
                               window (title, label, details heading,
                               field labels, chatlog toggle, resolve-as
                               heading, resolution buttons, release
                               back to queue), CFH chatlog title

The same 130 entries land in Nitro-Files/.../UITexts.json (runtime).
Both files validate as JSON. The runtime additions take effect on
next client reload; the template additions ship the strings to any
fresh deploy.

Notes:
  - The MOD_ACTION_DEFINITIONS sanction names ("Alert", "Mute 1h",
    "Ban 18h" …) stay hardcoded for now since they're keyed off
    server-side action IDs that don't have an existing locale key
    convention. Worth a follow-up if needed.
  - help.cfh.topic.* keys (CFH topic display names) are already in
    ExternalTexts.json and were already read via LocalizeText, so
    they didn't need changes.

typecheck + vitest 214/214 + lint:hooks all clean.
2026-05-20 21:41:52 +02:00
simoleo89 d3552a0948 refactor(mod-tools): redesign all related windows with shared visual language
Applies the visual language introduced in ModToolsUserView yesterday
to every other ModTools window. The design tokens used consistently:

  emerald — present in current room / positive state
  sky     — online / informational / current selection
  zinc    — neutral / disabled
  amber   — warn-level (CFH, alerts, cautions)
  rose    — danger (bans, releases, abusive)

Files redesigned:

ModToolsRoomView
  Identity header with FaDoorOpen, room name + ID, owner-present pill
  (emerald/zinc), manual refresh button. Stat strip: user count (sky)
  + clickable owner name (zinc) opening user info. Quick actions
  (Visit / Chatlog) in a 2-col grid. Moderate panel collapsed into an
  amber-tinted card with the 3 toggles + textarea + two CTAs (Send
  Caution=danger, Send Alert=warning). CTAs disabled until a message
  is typed AND the room info has loaded.

ModToolsUserModActionView
  Numbered 3-step form (CFH topic → sanction → optional message).
  Live preview row showing the chosen topic + sanction as tone-coded
  pills (amber/sky/rose/orange/fuchsia/zinc by action type). Primary
  CTA = Default Sanction, success CTA = Apply Sanction, both
  disabled until the required selections are made.

ModToolsUserSendMessageView
  Recipient header with FaEnvelope and the username, autofocused
  textarea, char counter, single full-width Send button gated on
  non-empty message.

ModToolsUserRoomVisitsView
  Header strip with entry count badge, three-column grid (time / room
  name / visit button), monospace timestamps, hover row highlight,
  empty state with FaDoorOpen icon.

ModToolsUserChatlogView / ModToolsChatlogView / CfhChatlogView
  Loading state with spinner instead of returning null. Cards grow to
  min-w-[460px] max-w-[520px] max-h-[500px] for usable chatlog area.

ChatlogView
  Replace Bootstrap-ish striped table with a CSS grid (60px / 120px /
  1fr). Room-info separator rendered as a sky card with Visit/Tools
  pill buttons. Per-row hover + even-row tint; highlighted rows
  (hasHighlighting) get an amber wash. Username is a button opening
  user info via existing link event. Empty state with FaCommentDots.

ModToolsTicketsView
  Tabs get icons (FaListUl / FaUserCheck / FaCheckSquare) and inline
  count badges (amber/sky/zinc) so the moderator sees the queue size
  at a glance. ticket bucket filtering memoized off the tickets array.

ModToolsOpenIssuesTabView / MyIssuesTabView / PickedIssuesTabView
  Same CSS grid table style. Category renders as a tone-coded pill
  (Open=amber, Mine=sky, All picked=zinc). Action buttons get icons
  (FaHandPointer Pick, FaTools Handle, FaSignOutAlt Release). Empty
  state with FaInbox.

ModToolsIssueInfoView
  Card header with category + topic pills. Details rendered as a dl
  grid instead of a striped table. Caller / Reported names as inline
  link buttons with external-link icon. Chatlog toggle is full-width
  secondary. Resolution buttons in a 3-col grid with intent colours
  (success=Resolved, dark=Useless, danger=Abusive) + a separate
  Release-to-queue button on its own row so it isn't confused with
  the resolutions.

No behaviour changes — all composers, message events, parent state
hookups, and sanction validation paths are unchanged. This is purely
a presentation pass. typecheck + vitest 214/214 + lint:hooks all
clean.
2026-05-20 21:41:52 +02:00
simoleo89 7ade398610 refactor(mod-tools): redesign ModToolsUserView template
Replace the flat striped table with a structured layout that surfaces
the moderation signal at a glance:

Identity header
  Username + ID + classification, presence pill (In room / Online /
  Offline) with colour coding (emerald / sky / zinc) and a matching
  dot, plus a manual refresh button. The pill source-of-truth is
  useRoomUserListSnapshot for the "in room" case (reactive) falling
  back to userInfo.online — tooltip discloses which path produced
  the value.

Stat strip
  Four counter cards in a single row — CFH, Cautions, Bans, Trade
  locks — tinted warn (amber) or danger (rose) when value > 0, neutral
  (zinc) when zero. Big tabular-nums numbers so the moderator sees a
  problem account immediately without parsing rows.

Sectioned body
  Account / Activity / Sanctions / Trading as labelled dl groups
  (grid-cols-[auto_1fr]) replacing the 14-row striped table. Missing
  values render as a dim em-dash instead of an empty cell.

Action bar
  2×2 button grid with react-icons/fa glyphs (FaCommentDots,
  FaEnvelope, FaDoorOpen, FaGavel). Mod Action keeps variant="danger"
  so the destructive action stands out from the three info actions
  (variant="secondary").

No behaviour changes — the same composer / event listeners /
sub-views are wired up; this is a presentation rewrite. Card grows
to min-w-[420px] max-w-[480px] to fit the new layout without
horizontal scroll on mod laptops.
2026-05-20 21:41:52 +02:00
simoleo89 ef313adcfa feat(mod-tools): reactive ModToolsUserView (online dot + refresh on sanction)
ModToolsUserView used a one-shot ModeratorUserInfoData snapshot taken at
panel-open time. Two consequences:

- The online/offline icon (rendered next to userName) was frozen on the
  value at open. If the target user joined/left while the panel stayed
  open, the icon kept lying.
- After the moderator applied a sanction via ModToolsUserModActionView
  the user info window stayed open with stale cfhCount / banCount /
  cautionCount / lastSanctionTime; you had to close and reopen to see
  the bump.

Fix shape mirrors the ModToolsView selected-user dot from yesterday:

- Read useRoomUserListSnapshot in the component (outside any useBetween
  scope — useSyncExternalStore constraint). If the target user is in
  the current room they're online; fall back to userInfo.online
  otherwise. Tooltip surfaces which path produced the value.
- Subscribe to ModeratorActionResultMessageEvent (parser carries
  userId + success). On a successful action targeting THIS userId,
  re-send GetModeratorUserInfoMessageComposer so the table re-fetches.
2026-05-20 21:41:51 +02:00
simoleo89 5c3589c29e feat(mod-tools): reactive box + bug fixes in useModTools
ModToolsView
- Subscribe to useRoomUserListSnapshot so the selected user's
  "still in room" state is reactive — green dot when the user is
  in the current room, gray dot when they've left. Previously the
  selection was a static capture at click time.
- Add an inline X to clear the selected-user slot without having
  to click a different avatar.
- Report Tool button shows a count badge for OPEN tickets
  (IssueMessageData.STATE_OPEN) so a new ticket arriving while
  the panel is open is visible immediately. Caps display at 99+.
- Tooltip on the room-bound buttons explains why they're disabled
  ("Enter a room first") instead of showing a silent disabled state.
- Buttons grow their labels with `flex-grow text-start` so the
  trailing dot / badge / clear-X sits flush right.

useModTools
- Fix splice(index) → splice(index, 1) in close{Room,RoomChatlog,
  UserInfo,UserChatlog} — the omitted second argument was
  silently deleting EVERY subsequent open panel, not just the one
  being closed. Visible whenever a moderator had two or more panels
  of the same kind open.
- Fix toggleUserChatlog reading from openRoomChatlogs instead of
  openUserChatlogs — copy-paste typo made the toggle inconsistent
  with the underlying state.
2026-05-19 22:12:19 +02:00
simoleo89 bf84a0c2a6 useNitroQuery: add accept() predicate; migrate two mod-tools chatlog views
Many composer/parser pairs on the Nitro wire are correlation-key based:
the request carries a key (roomId, issueId, etc.) and the response shows
up on the globally-shared event bus, where other components may be
listening for the same parser type with a different key. The previous
useNitroQuery resolved on the FIRST matching parser event regardless of
key — useless for that pattern, which is why two obvious migration
targets (ModToolsChatlogView, CfhChatlogView) were skipped earlier.

Adapter change
- New optional `accept?: (event) => boolean` on NitroQueryConfig.
- In awaitNitroResponse, events for which accept returns false are
  IGNORED rather than resolving the promise. The listener stays
  registered, the timeout still applies. This lets callers do:
    accept: e => e.getParser()?.data.roomId === roomId

Migrations
- src/components/mod-tools/views/room/ModToolsChatlogView.tsx
  - Was: useState<ChatRecordData>(null) + useMessageEvent with
    `if (parser.data.roomId !== roomId) return; setRoomChatlog(...)` +
    a mount-only useEffect dispatching the composer.
  - Now: a single useNitroQuery call keyed on roomId; accept filters
    by roomId; the query is enabled only when roomId is set.
    The composer is no longer re-dispatched on remount within
    staleTime; switching to a different room still triggers a fresh
    fetch because the queryKey changes.
- src/components/mod-tools/views/tickets/CfhChatlogView.tsx
  - Same pattern, keyed on issueId.

Both migrations drop ~15 lines per file (no more local state + manual
listener + manual send) while gaining cache/dedup/loading/error
handling from TanStack Query.

Verification
- yarn eslint on the four files: 1 pre-existing error (the
  IMessageEvent "redundant union" false positive in createNitroQuery
  that we already documented — local sandbox doesn't have the
  renderer SDK installed, so its types resolve as `any`).
- yarn test: 49/49 passing.
- yarn tsc on the four files: clean.
2026-05-11 17:00:06 +00:00
simoleo89 535fa71020 ESLint --fix: auto-fix brace-style, indent, semi, no-trailing-spaces
Run eslint --fix across src/ to clear ~1900 mechanical lint errors
surfaced by the @typescript-eslint v8 + react-hooks v7 + react-compiler
upgrade in the React 19 modernization PR.

Issues fixed automatically:
- brace-style (Allman): try/catch one-liners reformatted to multi-line
- indent: tab-vs-space and depth corrections
- semi: missing trailing semicolons
- no-trailing-spaces

No semantic changes. Remaining 701 errors are real-code issues
(set-state-in-effect, rules-of-hooks, no-unsafe-* type checks) that
need manual per-file review.

https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
2026-05-11 16:31:50 +00:00
duckietm 2a5c31ad8a Revert "Merge pull request #17 from simoleo89/mod-tools-i18n"
This reverts commit 3789f53ab3, reversing
changes made to 871670ec08.
2026-03-19 10:40:06 +01:00
simoleo89 349498ec34 Localize all hardcoded texts in mod tools using LocalizeText
Replace ~70 hardcoded English strings across 15 mod-tools files
with LocalizeText() calls using moderation.* keys matching the
existing ExternalTexts convention. Includes mod-tools-external-texts.json
with all required keys for ExternalTexts.json.
2026-03-18 20:46:07 +01:00
duckietm bffaccf6a3 🆙 Security Fix - Will not go into details 2026-03-18 16:52:32 +01:00
simoleo89 f213e89122 Improve mod tools UI layout and usability
- Fix icon alignment using flexbox instead of absolute positioning
- Add active state indicators on buttons when sub-panels are open
- Add min-width constraints to prevent cramped layouts
- Improve user button with placeholder text and truncated username
- Improve room info panel with better spacing, clickable owner, colored owner status
- Improve chatlog with scrollable container, alternating row colors, compact headers
- Clean up room info header and room name display
2026-03-17 13:01:31 +01:00
duckietm f2446d232b 🆙 Upgrade to tailwind css 4.2.0 2026-02-20 08:17:17 +01:00
DuckieTM 7feb10ab15 🆙 Init V3 2026-01-31 09:10:52 +01:00