/* * 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 ApiError, getErrorMessage} from '@fluxer/admin/src/api/Errors'; import {Grid} from '@fluxer/admin/src/components/ui/Grid'; import {Stack} from '@fluxer/admin/src/components/ui/Stack'; import {Heading, Text} from '@fluxer/admin/src/components/ui/Typography'; import type {AdminConfig as Config} from '@fluxer/admin/src/types/Config'; import type {ListUserDmChannelsResponse} from '@fluxer/schema/src/domains/admin/AdminUserSchemas'; import {Button} from '@fluxer/ui/src/components/Button'; import {Card} from '@fluxer/ui/src/components/Card'; import type {FC} from 'hono/jsx'; interface DmHistoryTabProps { config: Config; userId: string; dmChannelsResult: {ok: true; data: ListUserDmChannelsResponse} | {ok: false; error: ApiError} | null; before: string | null; after: string | null; limit: number; } export function DmHistoryTab({config, userId, dmChannelsResult, before, after, limit}: DmHistoryTabProps) { if (!dmChannelsResult) { return null; } if (!dmChannelsResult.ok) { return ( DM History Failed to load DM history: {getErrorMessage(dmChannelsResult.error)} ); } const channels = dmChannelsResult.data.channels; return ( {`DM History (${channels.length})`} Historical one-to-one DMs for this user. Group DMs are not included in this dataset. {channels.length === 0 ? ( No historical DM channels found. ) : ( )} ); } const DmChannelsGrid: FC<{ config: Config; userId: string; channels: ListUserDmChannelsResponse['channels']; }> = ({config, userId, channels}) => { return ( {channels.map((channel) => (
DM {channel.channel_id} {formatChannelType(channel.channel_type)}
Status: {channel.is_open ? 'Open' : 'Closed'} Last Message ID: {channel.last_message_id ?? 'None'}
))}
); }; const CounterpartyRow: FC<{config: Config; userId: string; recipientIds: Array}> = ({ config, userId, recipientIds, }) => { const counterpartyIds = recipientIds.filter((recipientId) => recipientId !== userId); if (counterpartyIds.length === 0) { return ( Counterparty: unavailable ); } return (
Counterparty:{' '} {counterpartyIds.map((recipientId, index) => ( {recipientId} {index < counterpartyIds.length - 1 ? ', ' : ''} ))}
); }; interface DmHistoryPaginationProps { config: Config; userId: string; channels: ListUserDmChannelsResponse['channels']; limit: number; before: string | null; after: string | null; } const DmHistoryPagination: FC = ({config, userId, channels, limit, before, after}) => { const hasNext = channels.length === limit; const hasPrevious = before !== null || after !== null; const firstChannel = channels[0]; const lastChannel = channels[channels.length - 1]; const firstId = firstChannel ? firstChannel.channel_id : null; const lastId = lastChannel ? lastChannel.channel_id : null; const prevPath = hasPrevious && firstId ? buildPaginationPath(config, userId, null, firstId, limit) : null; const nextPath = hasNext && lastId ? buildPaginationPath(config, userId, lastId, null, limit) : null; if (!prevPath && !nextPath) { return null; } return (
{prevPath && ( Previous )} {nextPath && ( Next )}
); }; function buildPaginationPath( config: Config, userId: string, before: string | null, after: string | null, limit: number, ): string { const params = [`tab=dm_history`, `dm_limit=${limit}`]; if (before) { params.push(`dm_before=${before}`); } if (after) { params.push(`dm_after=${after}`); } return `${config.basePath}/users/${userId}?${params.join('&')}`; } function buildMessageLookupHref(config: Config, channelId: string): string { const params = new URLSearchParams(); params.set('channel_id', channelId); params.set('context_limit', '50'); return `${config.basePath}/messages?${params.toString()}`; } function formatChannelType(channelType: number | null): string { if (channelType === 1) { return 'DM'; } if (channelType === 3) { return 'Group DM'; } return 'Unknown'; }