/* * 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 && }
); }