mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
46daa96100
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.
114 lines
7.1 KiB
TypeScript
114 lines
7.1 KiB
TypeScript
import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
|
|
import { FC, useState } from 'react';
|
|
import { FaBan, FaCheck, FaCommentDots, FaExternalLinkAlt, FaSignOutAlt, FaTrashAlt } from 'react-icons/fa';
|
|
import { GetIssueCategoryName, LocalizeText, SendMessageComposer } from '../../../../api';
|
|
import { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
|
import { useModTools } from '../../../../hooks';
|
|
import { CfhChatlogView } from './CfhChatlogView';
|
|
|
|
interface IssueInfoViewProps
|
|
{
|
|
issueId: number;
|
|
onIssueInfoClosed(issueId: number): void;
|
|
}
|
|
|
|
const Field: FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => (
|
|
<>
|
|
<dt className="opacity-60 whitespace-nowrap">{ label }</dt>
|
|
<dd className="m-0 break-words font-medium">{ children || <span className="opacity-40">-</span> }</dd>
|
|
</>
|
|
);
|
|
|
|
export const ModToolsIssueInfoView: FC<IssueInfoViewProps> = props =>
|
|
{
|
|
const { issueId = null, onIssueInfoClosed = null } = props;
|
|
const [ cfhChatlogOpen, setCfhChatlogOpen ] = useState(false);
|
|
const { tickets = [], openUserInfo = null } = useModTools();
|
|
const ticket = tickets.find(issue => (issue.issueId === issueId));
|
|
|
|
const releaseIssue = () =>
|
|
{
|
|
SendMessageComposer(new ReleaseIssuesMessageComposer([ issueId ]));
|
|
onIssueInfoClosed(issueId);
|
|
};
|
|
|
|
const closeIssue = (resolutionType: number) =>
|
|
{
|
|
SendMessageComposer(new CloseIssuesMessageComposer([ issueId ], resolutionType));
|
|
onIssueInfoClosed(issueId);
|
|
};
|
|
|
|
if(!ticket) return null;
|
|
|
|
return (
|
|
<>
|
|
<NitroCardView className="nitro-mod-tools-handle-issue min-w-[440px] max-w-[500px]" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
|
<NitroCardHeaderView headerText={ LocalizeText('modtools.tickets.issue.title', [ 'issueId' ], [ issueId.toString() ]) } onCloseClick={ () => onIssueInfoClosed(issueId) } />
|
|
<NitroCardContentView className="text-black" gap={ 2 }>
|
|
{/* Issue header */}
|
|
<div className="flex items-center gap-2 bg-gradient-to-r from-amber-50 to-transparent rounded p-2 border border-amber-100">
|
|
<FaCommentDots className="text-amber-600 shrink-0" size={ 16 } />
|
|
<div className="flex flex-col grow min-w-0">
|
|
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold">{ LocalizeText('modtools.tickets.issue.label', [ 'issueId' ], [ issueId.toString() ]) }</div>
|
|
<div className="font-semibold leading-tight truncate">{ GetIssueCategoryName(ticket.categoryId) }</div>
|
|
</div>
|
|
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border bg-white border-amber-200 text-amber-800">
|
|
{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }
|
|
</span>
|
|
</div>
|
|
|
|
{/* Details */}
|
|
<div className="flex flex-col gap-1">
|
|
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold border-b border-zinc-200 pb-1 mb-0.5">{ LocalizeText('modtools.tickets.issue.details') }</div>
|
|
<dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-[.8rem] m-0">
|
|
<Field label={ LocalizeText('modtools.tickets.issue.field.source') }>{ GetIssueCategoryName(ticket.categoryId) }</Field>
|
|
<Field label={ LocalizeText('modtools.tickets.issue.field.category') }>{ LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId) }</Field>
|
|
<Field label={ LocalizeText('modtools.tickets.issue.field.description') }>{ ticket.message }</Field>
|
|
<Field label={ LocalizeText('modtools.tickets.issue.field.caller') }>
|
|
<button
|
|
className="font-semibold text-sky-700 hover:text-sky-900 hover:underline inline-flex items-center gap-1"
|
|
onClick={ () => openUserInfo(ticket.reporterUserId) }>
|
|
{ ticket.reporterUserName } <FaExternalLinkAlt size={ 8 } className="opacity-60" />
|
|
</button>
|
|
</Field>
|
|
<Field label={ LocalizeText('modtools.tickets.issue.field.reported') }>
|
|
<button
|
|
className="font-semibold text-sky-700 hover:text-sky-900 hover:underline inline-flex items-center gap-1"
|
|
onClick={ () => openUserInfo(ticket.reportedUserId) }>
|
|
{ ticket.reportedUserName } <FaExternalLinkAlt size={ 8 } className="opacity-60" />
|
|
</button>
|
|
</Field>
|
|
</dl>
|
|
</div>
|
|
|
|
{/* Tools */}
|
|
<Button gap={ 1 } variant="secondary" onClick={ () => setCfhChatlogOpen(prev => !prev) }>
|
|
<FaCommentDots size={ 12 } /> { cfhChatlogOpen ? LocalizeText('modtools.tickets.issue.chatlog.close') : LocalizeText('modtools.tickets.issue.chatlog.view') }
|
|
</Button>
|
|
|
|
{/* Resolution buttons */}
|
|
<div className="flex flex-col gap-1.5 pt-1 border-t border-zinc-200">
|
|
<div className="text-[.7rem] uppercase tracking-wide opacity-60 font-semibold">{ LocalizeText('modtools.tickets.issue.resolve.heading') }</div>
|
|
<div className="grid grid-cols-3 gap-1.5">
|
|
<Button gap={ 1 } variant="success" onClick={ () => closeIssue(CloseIssuesMessageComposer.RESOLUTION_RESOLVED) }>
|
|
<FaCheck size={ 11 } /> { LocalizeText('modtools.tickets.issue.resolve.resolved') }
|
|
</Button>
|
|
<Button gap={ 1 } variant="dark" onClick={ () => closeIssue(CloseIssuesMessageComposer.RESOLUTION_USELESS) }>
|
|
<FaTrashAlt size={ 11 } /> { LocalizeText('modtools.tickets.issue.resolve.useless') }
|
|
</Button>
|
|
<Button gap={ 1 } variant="danger" onClick={ () => closeIssue(CloseIssuesMessageComposer.RESOLUTION_ABUSIVE) }>
|
|
<FaBan size={ 11 } /> { LocalizeText('modtools.tickets.issue.resolve.abusive') }
|
|
</Button>
|
|
</div>
|
|
<Button gap={ 1 } variant="secondary" onClick={ releaseIssue }>
|
|
<FaSignOutAlt size={ 12 } /> { LocalizeText('modtools.tickets.issue.release') }
|
|
</Button>
|
|
</div>
|
|
</NitroCardContentView>
|
|
</NitroCardView>
|
|
{ cfhChatlogOpen &&
|
|
<CfhChatlogView issueId={ issueId } onCloseClick={ () => setCfhChatlogOpen(false) } /> }
|
|
</>
|
|
);
|
|
};
|