/*
* 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 {GuildChannel, GuildLookupResult, GuildRole} from '@fluxer/admin/src/api/Guilds';
import {Badge} from '@fluxer/admin/src/components/ui/Badge';
import {EmptyState} from '@fluxer/admin/src/components/ui/EmptyState';
import {HStack} from '@fluxer/admin/src/components/ui/Layout/HStack';
import {VStack} from '@fluxer/admin/src/components/ui/Layout/VStack';
import {Heading, Text} from '@fluxer/admin/src/components/ui/Typography';
import type {AdminConfig as Config} from '@fluxer/admin/src/types/Config';
import {FLUXER_EPOCH} from '@fluxer/constants/src/Core';
import {Button} from '@fluxer/ui/src/components/Button';
import {Card} from '@fluxer/ui/src/components/Card';
import {CsrfInput} from '@fluxer/ui/src/components/CsrfInput';
import {InfoGrid, InfoItem} from '@fluxer/ui/src/components/Layout';
import {
getGuildBannerUrl,
getGuildEmbedSplashUrl,
getGuildIconUrl,
getGuildSplashUrl,
} from '@fluxer/ui/src/utils/FormatUser';
import type {FC} from 'hono/jsx';
function getCurrentSnowflake(): string {
const now = Date.now();
const timestampOffset = now - FLUXER_EPOCH;
const snowflake = BigInt(timestampOffset) * 4_194_304n;
return snowflake.toString();
}
function channelTypeToString(type: number): string {
switch (type) {
case 0:
return 'Text';
case 2:
return 'Voice';
case 4:
return 'Category';
default:
return `Unknown (${type})`;
}
}
function intToHex(i: number): string {
if (i === 0) return '000000';
const r = Math.floor(i / 65536) % 256;
const g = Math.floor(i / 256) % 256;
const b = i % 256;
return byteToHex(r) + byteToHex(g) + byteToHex(b);
}
function byteToHex(byte: number): string {
const hexDigits = '0123456789ABCDEF';
const high = Math.floor(byte / 16);
const low = byte % 16;
return (hexDigits[high] ?? '0') + (hexDigits[low] ?? '0');
}
interface OverviewTabProps {
config: Config;
guild: GuildLookupResult;
csrfToken: string;
}
const RenderChannel: FC<{config: Config; channel: GuildChannel}> = ({config, channel}) => {
const currentSnowflake = getCurrentSnowflake();
return (
{channel.name}
{channel.id}
{channelTypeToString(channel.type)}
);
};
const RenderRole: FC<{role: GuildRole}> = ({role}) => {
const colorHex = intToHex(role.color);
return (
{role.name}
{role.id}
{role.hoist && Hoisted}
{role.mentionable && Mentionable}
);
};
const RenderSearchIndexButton: FC<{
config: Config;
guildId: string;
title: string;
indexType: string;
csrfToken: string;
}> = ({config, guildId, title, indexType, csrfToken}) => {
return (
);
};
const AssetPreview: FC<{
label: string;
url: string | null;
hash: string | null;
variant: 'square' | 'wide';
}> = ({label, url, hash, variant}) => {
const imageClass =
variant === 'square'
? 'h-24 w-24 rounded bg-neutral-100 object-cover'
: 'h-36 w-full rounded bg-neutral-100 object-cover';
return (
{label}
{url ? (
) : (
Not set
)}
Hash: {hash ?? 'null'}
);
};
export function OverviewTab({config, guild, csrfToken}: OverviewTabProps) {
const sortedChannels = [...guild.channels].sort((a, b) => a.position - b.position);
const sortedRoles = [...guild.roles].sort((a, b) => b.position - a.position);
const iconUrl = getGuildIconUrl(config.mediaEndpoint, guild.id, guild.icon, true);
const bannerUrl = getGuildBannerUrl(config.mediaEndpoint, guild.id, guild.banner, true);
const splashUrl = getGuildSplashUrl(config.mediaEndpoint, guild.id, guild.splash);
const embedSplashUrl = getGuildEmbedSplashUrl(config.mediaEndpoint, guild.id, guild.embed_splash);
return (
Assets
Guild Information
Owner ID
{guild.owner_id}
Features
{guild.features.length === 0 ? (
No features enabled
) : (
{guild.features.map((feature) => (
{feature}
))}
)}
Channels ({guild.channels.length})
{guild.channels.length === 0 ? (
No channels
) : (
{sortedChannels.map((channel) => (
))}
)}
Roles ({guild.roles.length})
{guild.roles.length === 0 ? (
No roles
) : (
{sortedRoles.map((role) => (
))}
)}
Search Index Management
Refresh search indexes for this guild.
);
}