/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
/** @jsxRuntime automatic */
/** @jsxImportSource hono/jsx */
import type {Report} from '@fluxer/admin/src/api/Reports';
import {Layout} from '@fluxer/admin/src/components/Layout';
import {createMessageDeletionScriptBody, MessageList} from '@fluxer/admin/src/components/MessageList';
import {VStack} from '@fluxer/admin/src/components/ui/Layout/VStack';
import {NavLink} from '@fluxer/admin/src/components/ui/NavLink';
import {ResourceLink} from '@fluxer/admin/src/components/ui/ResourceLink';
import {TextLink} from '@fluxer/admin/src/components/ui/TextLink';
import {Caption, Heading, Text} from '@fluxer/admin/src/components/ui/Typography';
import type {Session} from '@fluxer/admin/src/types/App';
import type {AdminConfig as Config} from '@fluxer/admin/src/types/Config';
import {formatTimestamp} from '@fluxer/date_utils/src/DateFormatting';
import type {Flash} from '@fluxer/hono/src/Flash';
import type {UserAdminResponse} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
import {Button} from '@fluxer/ui/src/components/Button';
import {CsrfInput} from '@fluxer/ui/src/components/CsrfInput';
import {FlexRow} from '@fluxer/ui/src/components/Layout';
import type {FC} from 'hono/jsx';
interface MessageContext {
id: string;
channel_id: string;
guild_id: string | null;
content: string;
timestamp: string;
attachments: Array<{filename: string; url: string}>;
author_id: string;
author_username: string;
author_discriminator: string;
}
export interface ReportDetailPageProps {
config: Config;
session: Session;
currentAdmin: UserAdminResponse | undefined;
flash: Flash | undefined;
assetVersion: string;
report: Report;
csrfToken: string;
}
function formatTimestampLocal(timestamp: string): string {
return formatTimestamp(timestamp, 'en-US', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
}
function formatReportType(reportType: number): string {
switch (reportType) {
case 0:
return 'Message';
case 1:
return 'User';
case 2:
return 'Guild';
default:
return 'Unknown';
}
}
function buildMessageLookupHref(config: Config, channelId: string, messageId: string | null): string {
const params = new URLSearchParams();
params.set('channel_id', channelId);
params.set('context_limit', '50');
if (messageId) {
params.set('message_id', messageId);
}
return `${config.basePath}/messages?${params.toString()}`;
}
function formatReportedUserLabel(report: Report): string {
if (report.reported_user_tag) {
return report.reported_user_tag;
}
if (report.reported_user_username) {
const discriminator = report.reported_user_discriminator ?? '0000';
return `${report.reported_user_username}#${discriminator}`;
}
return `User ${report.reported_user_id ?? 'unknown'}`;
}
const InfoRow: FC<{label: string; value: string; mono?: boolean}> = ({label, value, mono}) => (
{label}
{value}
);
const InfoRowWithLink: FC<{
label: string;
value: string;
href: string;
mono?: boolean;
}> = ({label, value, href, mono}) => (
{label}
{value}
);
const InfoRowOpt: FC<{label: string; value: string | null; mono?: boolean}> = ({label, value, mono}) => (
{label}
{value ?? '\u2014'}
);
const InfoRowOptWithLink: FC<{
config: Config;
label: string;
id: string | null;
name: string | null;
pathFn: (id: string) => string;
mono?: boolean;
}> = ({config, label, id, name, pathFn, mono}) => (
{label}
{id ? (
{name ?? id}
) : (
{'\u2014'}
)}
);
const BasicInfo: FC<{config: Config; report: Report}> = ({config, report}) => {
const reporterPrimary = report.reporter_tag ?? report.reporter_email ?? 'Anonymous';
return (
Basic Information
{report.reporter_id ? (
) : (
)}
);
};
const ReportedEntity: FC<{config: Config; report: Report}> = ({config, report}) => {
const renderMessageReportEntity = () => (
<>
`/users/${id}`}
mono
/>
{report.reported_message_id && report.reported_channel_id ? (
) : (
)}
{report.reported_channel_id ? (
) : (
)}
`/guilds/${id}`}
mono
/>
>
);
const renderUserReportEntity = () => (
<>
`/users/${id}`}
mono
/>
`/guilds/${id}`}
mono
/>
>
);
const renderGuildReportEntity = () => (
<>
`/guilds/${id}`}
mono
/>
>
);
return (
Reported Entity
{report.report_type === 0 && renderMessageReportEntity()}
{report.report_type === 1 && renderUserReportEntity()}
{report.report_type === 2 && renderGuildReportEntity()}
);
};
const MessageContextList: FC<{config: Config; messages: Array}> = ({config, messages}) => {
if (messages.length === 0) return null;
const mappedMessages = messages.map((msg) => ({
id: msg.id,
content: msg.content || '',
timestamp: formatTimestampLocal(msg.timestamp),
author_id: msg.author_id,
author_username: msg.author_username,
author_discriminator: msg.author_discriminator,
channel_id: msg.channel_id,
guild_id: msg.guild_id,
attachments: msg.attachments,
}));
return (
Message Context
);
};
const AdditionalInfo: FC<{info: string}> = ({info}) => (
Additional Information
{info}
);
const StatusCard: FC<{config: Config; report: Report}> = ({config, report}) => (
Status
{report.status === 0 && (
Pending
)}
{report.status === 1 && Resolved}
{report.status !== 0 && report.status !== 1 && (
Unknown
)}
{report.resolved_at && (
Resolved At:
{formatTimestampLocal(report.resolved_at)}
)}
{report.resolved_by_admin_id && (
Resolved By:
{report.resolved_by_admin_id}
)}
{report.public_comment && (
Public Comment:
{report.public_comment}
)}
);
const ActionsCard: FC<{config: Config; report: Report; csrfToken: string}> = ({config, report, csrfToken}) => {
const renderResolveButton = () => {
if (report.status !== 0) return null;
return (
);
};
const renderViewReportedEntityButton = () => {
if (report.report_type === 0 || report.report_type === 1) {
if (report.reported_user_id) {
return (
View Reported User
);
}
}
if (report.report_type === 2) {
if (report.reported_guild_id) {
return (
View Reported Guild
);
}
}
return null;
};
const renderViewReporterButton = () => {
if (!report.reporter_id) return null;
return (
View Reporter
);
};
const renderViewMutualDmButton = () => {
if (report.report_type !== 1) {
return null;
}
if (!report.mutual_dm_channel_id) {
return null;
}
return (
View Mutual DM Channel
);
};
return (
Actions
{renderResolveButton()}
{renderViewReportedEntityButton()}
{renderViewReporterButton()}
{renderViewMutualDmButton()}
);
};
export function ReportDetailPage({
config,
session,
currentAdmin,
flash,
assetVersion,
report,
csrfToken,
}: ReportDetailPageProps) {
const messageContext = report.message_context ?? [];
const hasMessageContext = report.report_type === 0 && messageContext.length > 0;
return (
{'\u2190'} Back to Reports
Report Details
{hasMessageContext &&
}
{report.additional_info &&
}
);
}
export function ReportDetailFragment({config, report}: {config: Config; report: Report}) {
const messageContext = report.message_context ?? [];
const hasMessageContext = report.report_type === 0 && messageContext.length > 0;
return (
{hasMessageContext &&
}
{report.additional_info &&
}
);
}