diff --git a/src/components/mod-tools/views/user/ModToolsUserView.tsx b/src/components/mod-tools/views/user/ModToolsUserView.tsx index 8a2620d..8b03395 100644 --- a/src/components/mod-tools/views/user/ModToolsUserView.tsx +++ b/src/components/mod-tools/views/user/ModToolsUserView.tsx @@ -1,7 +1,8 @@ import { CreateLinkEvent, GetModeratorUserInfoMessageComposer, ModeratorActionResultMessageEvent, ModeratorUserInfoData, ModeratorUserInfoEvent } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useState } from 'react'; +import { FaBan, FaCommentDots, FaDoorOpen, FaEnvelope, FaExchangeAlt, FaExclamationTriangle, FaGavel, FaSync } from 'react-icons/fa'; import { FriendlyTime, LocalizeText, SendMessageComposer } from '../../../../api'; -import { Button, Column, DraggableWindowPosition, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; +import { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { useMessageEvent, useRoomUserListSnapshot } from '../../../../hooks'; import { ModToolsUserModActionView } from './ModToolsUserModActionView'; import { ModToolsUserRoomVisitsView } from './ModToolsUserRoomVisitsView'; @@ -13,6 +14,52 @@ interface ModToolsUserViewProps onCloseClick: () => void; } +interface StatCardProps +{ + icon: React.ReactNode; + label: string; + value: number | string; + tone?: 'neutral' | 'warn' | 'danger'; +} + +const StatCard: FC = ({ icon, label, value, tone = 'neutral' }) => +{ + const numericValue = typeof value === 'number' ? value : parseInt(value as string, 10); + const isElevated = !Number.isNaN(numericValue) && numericValue > 0; + const toneClasses = (() => + { + if(tone === 'danger' && isElevated) return 'bg-rose-50 border-rose-200 text-rose-700'; + if(tone === 'warn' && isElevated) return 'bg-amber-50 border-amber-200 text-amber-700'; + return 'bg-zinc-50 border-zinc-200 text-zinc-700'; + })(); + + return ( +
+
+ { icon } + { label } +
+
{ value }
+
+ ); +}; + +const Section: FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => ( +
+
{ title }
+
+ { children } +
+
+); + +const Field: FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => ( + <> +
{ label }
+
{ value || }
+ +); + export const ModToolsUserView: FC = props => { const { onCloseClick = null, userId = null } = props; @@ -29,71 +76,19 @@ export const ModToolsUserView: FC = props => [ roomUserList, userId ] ); const isOnline = isPresentInCurrentRoom || !!(userInfo && userInfo.online); + const presenceLabel = isPresentInCurrentRoom ? 'In room' : (isOnline ? 'Online' : 'Offline'); + const presencePillClass = isPresentInCurrentRoom + ? 'bg-emerald-100 text-emerald-700 border-emerald-200' + : isOnline + ? 'bg-sky-100 text-sky-700 border-sky-200' + : 'bg-zinc-100 text-zinc-600 border-zinc-200'; + const presenceDotClass = isPresentInCurrentRoom + ? 'bg-emerald-500' + : isOnline + ? 'bg-sky-500' + : 'bg-zinc-400'; - const userProperties = useMemo(() => - { - if(!userInfo) return null; - - return [ - { - localeKey: 'modtools.userinfo.userName', - value: userInfo.userName, - showOnline: true - }, - { - localeKey: 'modtools.userinfo.cfhCount', - value: userInfo.cfhCount.toString() - }, - { - localeKey: 'modtools.userinfo.abusiveCfhCount', - value: userInfo.abusiveCfhCount.toString() - }, - { - localeKey: 'modtools.userinfo.cautionCount', - value: userInfo.cautionCount.toString() - }, - { - localeKey: 'modtools.userinfo.banCount', - value: userInfo.banCount.toString() - }, - { - localeKey: 'modtools.userinfo.lastSanctionTime', - value: userInfo.lastSanctionTime - }, - { - localeKey: 'modtools.userinfo.tradingLockCount', - value: userInfo.tradingLockCount.toString() - }, - { - localeKey: 'modtools.userinfo.tradingExpiryDate', - value: userInfo.tradingExpiryDate - }, - { - localeKey: 'modtools.userinfo.minutesSinceLastLogin', - value: FriendlyTime.format(userInfo.minutesSinceLastLogin * 60, '.ago', 2) - }, - { - localeKey: 'modtools.userinfo.lastPurchaseDate', - value: userInfo.lastPurchaseDate - }, - { - localeKey: 'modtools.userinfo.primaryEmailAddress', - value: userInfo.primaryEmailAddress - }, - { - localeKey: 'modtools.userinfo.identityRelatedBanCount', - value: userInfo.identityRelatedBanCount.toString() - }, - { - localeKey: 'modtools.userinfo.registrationAgeInMinutes', - value: FriendlyTime.format(userInfo.registrationAgeInMinutes * 60, '.ago', 2) - }, - { - localeKey: 'modtools.userinfo.userClassification', - value: userInfo.userClassification - } - ]; - }, [ userInfo ]); + const refresh = () => SendMessageComposer(new GetModeratorUserInfoMessageComposer(userId)); useMessageEvent(ModeratorUserInfoEvent, event => { @@ -114,7 +109,7 @@ export const ModToolsUserView: FC = props => if(!parser || !parser.success || parser.userId !== userId) return; - SendMessageComposer(new GetModeratorUserInfoMessageComposer(userId)); + refresh(); }); useEffect(() => @@ -126,45 +121,73 @@ export const ModToolsUserView: FC = props => return ( <> - + onCloseClick() } /> - - - - - - { userProperties.map( (property, index) => - { + + {/* Identity header: name + presence pill + manual refresh */} +
+
+ { userInfo.userName } + ID #{ userInfo.userId }{ userInfo.userClassification ? ` · ${ userInfo.userClassification }` : '' } +
+ + + { presenceLabel } + + +
- return ( -
- - - - ); - }) } - -
{ LocalizeText(property.localeKey) } - { property.value } - { property.showOnline && - } -
-
- - - - - - -
+ {/* Moderation stat strip */} +
+ } label="CFH" tone="warn" value={ userInfo.cfhCount } /> + } label="Cautions" tone="warn" value={ userInfo.cautionCount } /> + } label="Bans" tone="danger" value={ userInfo.banCount } /> + } label="Trade locks" tone="danger" value={ userInfo.tradingLockCount } /> +
+ + {/* Body sections */} +
+
+ + + +
+
+ + +
+
+ + + +
+
+ +
+
+ + {/* Action bar */} +
+ + + + +
{ sendMessageVisible &&