Revert "feat: migrate frontend to Astro + expand AI moderation + backend admin/runtime config"

This reverts commit d59b59a7a7.
This commit is contained in:
asepharyana
2026-07-02 03:54:44 +07:00
parent 9636087e99
commit 7efaf00c93
91 changed files with 670 additions and 11161 deletions
@@ -1,9 +1,8 @@
import { AnimatePresence, motion } from "framer-motion";
import { motion } from "framer-motion";
import type { ReactNode } from "react";
import type { MessageRecord } from "../entities/message/types.js";
import type { DashboardTab } from "../entities/ui/types.js";
import type { VoiceStatus } from "../entities/voice/types.js";
import type { ThemeMode } from "../hooks/useTheme";
import { fadeSlideUp } from "../shared/hooks/useFramerStagger";
import type { WsStatus } from "../shared/ws/socket";
import { Header } from "./Header";
@@ -14,30 +13,22 @@ interface DashboardLayoutProps {
activeTab: DashboardTab;
wsStatus: WsStatus;
voiceStatus: VoiceStatus;
themeMode: ThemeMode;
isDark: boolean;
onTabChange: (tab: DashboardTab) => void;
onThemeToggle: () => void;
children: ReactNode;
recentMessages?: MessageRecord[];
guildId?: string;
channelId?: string;
notificationCount?: number;
}
export function DashboardLayout({
activeTab,
wsStatus,
voiceStatus,
themeMode,
isDark,
onTabChange,
onThemeToggle,
children,
recentMessages = [],
guildId,
channelId,
notificationCount = 0,
}: DashboardLayoutProps) {
return (
<div className="relative min-h-screen bg-background text-foreground">
@@ -55,29 +46,23 @@ export function DashboardLayout({
recentMessages={recentMessages}
guildId={guildId}
channelId={channelId}
notificationCount={notificationCount}
/>
<main className="flex min-w-0 flex-1 flex-col">
<Header
activeTab={activeTab}
wsStatus={wsStatus}
voiceStatus={voiceStatus}
themeMode={themeMode}
isDark={isDark}
onThemeToggle={onThemeToggle}
/>
<AnimatePresence mode="wait">
<motion.main
key={activeTab}
variants={fadeSlideUp}
initial="initial"
animate="animate"
exit="exit"
className="flex-1 overflow-auto p-4 md:p-6 lg:p-8 pb-16 md:pb-0"
>
{children}
</motion.main>
</AnimatePresence>
<motion.main
key={activeTab}
variants={fadeSlideUp}
initial="initial"
animate="animate"
exit="exit"
className="flex-1 overflow-auto p-4 md:p-6 lg:p-8"
>
{children}
</motion.main>
</main>
</div>
</div>
+3 -23
View File
@@ -1,8 +1,7 @@
import { motion } from "framer-motion";
import { Moon, Sun, Wifi, WifiOff } from "lucide-react";
import { Wifi, WifiOff } from "lucide-react";
import type { DashboardTab } from "../entities/ui/types.js";
import type { VoiceStatus } from "../entities/voice/types.js";
import type { ThemeMode } from "../hooks/useTheme";
import { fadeSlideUp } from "../shared/hooks/useFramerStagger";
import { cn } from "../shared/lib/utils";
import { Badge } from "../shared/ui";
@@ -12,23 +11,18 @@ const titles: Record<DashboardTab, string> = {
messages: "Messages & Moderation",
live: "Voice & Media",
dashboard: "Dashboard",
settings: "Admin Settings",
};
const subtitles: Record<DashboardTab, string> = {
messages: "Capture, analyse, and moderate Discord messages.",
live: "Join voice channels, play media, stream audio, and browse recordings.",
dashboard: "Server statistics, user profiles, and AI moderation overview.",
settings: "Manage dashboard visibility, runtime configuration, and authentication.",
};
interface HeaderProps {
activeTab: DashboardTab;
wsStatus: WsStatus;
voiceStatus: VoiceStatus;
themeMode: ThemeMode;
isDark: boolean;
onThemeToggle: () => void;
}
/** Dot indicator colour for WS badge */
@@ -69,7 +63,7 @@ function VoiceIndicator({ voiceStatus }: { voiceStatus: VoiceStatus }) {
);
}
export function Header({ activeTab, wsStatus, voiceStatus, themeMode, isDark, onThemeToggle }: HeaderProps) {
export function Header({ activeTab, wsStatus, voiceStatus }: HeaderProps) {
return (
<header className="sticky top-0 z-10 border-b border-border/50 bg-background/70 px-4 py-4 backdrop-blur-sm md:px-8">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
@@ -98,22 +92,8 @@ export function Header({ activeTab, wsStatus, voiceStatus, themeMode, isDark, on
</p>
</motion.div>
{/* Right: status badges + theme toggle */}
{/* Right: status badges */}
<div className="flex flex-wrap items-center gap-2">
{/* Theme toggle */}
<button
onClick={onThemeToggle}
className="flex items-center gap-1.5 rounded-lg border border-border bg-card/50 px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
title={`Switch to ${isDark ? "light" : "dark"} mode`}
>
{isDark ? (
<Sun className="h-3.5 w-3.5 text-amber-400" />
) : (
<Moon className="h-3.5 w-3.5 text-indigo-400" />
)}
<span className="hidden sm:inline">{isDark ? "Light" : "Dark"}</span>
</button>
{/* WS Badge */}
<Badge
variant="outline"
+9 -44
View File
@@ -1,11 +1,5 @@
import { motion } from "framer-motion";
import {
Bell,
LayoutDashboard,
MessageSquare,
Radio,
Settings,
} from "lucide-react";
import { LayoutDashboard, MessageSquare, Radio } from "lucide-react";
import type { MessageRecord } from "../entities/message/types.js";
import type { DashboardTab } from "../entities/ui/types.js";
import { useMascotChat } from "../shared/hooks/useMascotChat";
@@ -13,16 +7,12 @@ import { cn } from "../shared/lib/utils";
import { MascotChatbot } from "./mascot/MascotChatbot";
import { MascotImage } from "./mascot/MascotImage";
const navItems: Array<{
id: DashboardTab;
label: string;
icon: typeof Radio;
}> = [
{ id: "messages", label: "Messages & Moderation", icon: MessageSquare },
{ id: "live", label: "Voice & Media", icon: Radio },
{ id: "dashboard", label: "Dashboard", icon: LayoutDashboard },
{ id: "settings" as const, label: "Admin", icon: Settings },
];
const navItems: Array<{ id: DashboardTab; label: string; icon: typeof Radio }> =
[
{ id: "messages", label: "Messages & Moderation", icon: MessageSquare },
{ id: "live", label: "Voice & Media", icon: Radio },
{ id: "dashboard", label: "Dashboard", icon: LayoutDashboard },
];
interface SidebarProps {
activeTab: DashboardTab;
@@ -31,7 +21,6 @@ interface SidebarProps {
recentMessages?: MessageRecord[];
guildId?: string;
channelId?: string;
notificationCount?: number;
}
export function Sidebar({
@@ -41,7 +30,6 @@ export function Sidebar({
recentMessages = [],
guildId,
channelId,
notificationCount = 0,
}: SidebarProps) {
const mascotChat = useMascotChat({
messageCount: recentMessages.length,
@@ -58,7 +46,7 @@ export function Sidebar({
<motion.nav
className={cn(
"relative hidden shrink-0 flex-col overflow-visible border-r border-border/50 bg-background/70 backdrop-blur-sm transition-all duration-300 md:flex",
collapsed ? "w-16" : "w-56 lg:w-64",
collapsed ? "w-16" : "w-64",
)}
layout
transition={{ type: "spring", stiffness: 300, damping: 30 }}
@@ -74,10 +62,6 @@ export function Sidebar({
src="https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/docs/logo.svg"
alt="IMPHNEN"
className="h-8 w-8 rounded-xl"
onError={(e) => {
const target = e.currentTarget;
target.style.display = "none";
}}
/>
{/* Mascot image — only when expanded */}
@@ -86,10 +70,6 @@ export function Sidebar({
src="https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/apps/dimentorin/public/image/mascot-1.png"
alt="Mascot"
className="mt-4 h-auto w-[140px] object-contain drop-shadow-md"
onError={(e) => {
const target = e.currentTarget;
target.style.display = "none";
}}
/>
)}
</div>
@@ -115,21 +95,6 @@ export function Sidebar({
>
<Icon className="h-4 w-4 shrink-0" />
{!collapsed && <span>{item.label}</span>}
{!collapsed && item.id === "messages" &&
notificationCount !== undefined &&
notificationCount > 0 && (
<span className="ml-auto flex h-5 min-w-5 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-bold text-destructive-foreground">
{notificationCount > 99 ? "99+" : notificationCount}
</span>
)}
{/* Collapsed badge — top-right dot */}
{collapsed && item.id === "messages" &&
notificationCount !== undefined &&
notificationCount > 0 && (
<span className="absolute -right-0.5 -top-0.5 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-destructive text-[7px] font-bold text-destructive-foreground">
{notificationCount > 9 ? "N" : notificationCount}
</span>
)}
</button>
);
})}
@@ -153,7 +118,7 @@ export function Sidebar({
onClose={() => mascotChat.setIsOpen(false)}
onSendMessage={mascotChat.handleSendMessage}
mascotName="IMPHNEN Mascot"
className="fixed bottom-[170px] left-[80px] z-[9999] md:bottom-4 md:left-4 md:right-auto"
className="fixed bottom-[170px] left-[80px] z-[9999]"
/>
</>
);
@@ -117,14 +117,14 @@ export function MascotChatbot({
)}
>
{/* Header */}
<div className="bg-gradient-to-r from-primary to-primary/80 text-primary-foreground p-4 flex items-center justify-between">
<div className="bg-gradient-to-r from-primary to-primary/80 text-white p-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-primary-foreground/20 flex items-center justify-center">
<div className="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center">
<MessageCircle className="h-5 w-5" />
</div>
<div>
<h3 className="font-semibold text-sm">{mascotName}</h3>
<p className="text-xs text-primary-foreground/80">
<p className="text-xs text-white/80">
{loading ? "Mengetik..." : "Online"}
</p>
</div>
@@ -134,7 +134,7 @@ export function MascotChatbot({
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
onClick={() => setIsMinimized(!isMinimized)}
className="p-1.5 hover:bg-primary-foreground/20 rounded-lg transition-colors"
className="p-1.5 hover:bg-white/20 rounded-lg transition-colors"
title={isMinimized ? "Maximize" : "Minimize"}
>
{isMinimized ? (
@@ -149,7 +149,7 @@ export function MascotChatbot({
onClick={() => {
onClose?.();
}}
className="p-1.5 hover:bg-primary-foreground/20 rounded-lg transition-colors"
className="p-1.5 hover:bg-white/20 rounded-lg transition-colors"
title="Close"
>
<X className="h-4 w-4" />
@@ -176,10 +176,6 @@ export function MascotChatbot({
src={mascotAvatar}
alt={mascotName}
className="w-6 h-6 rounded-full object-cover"
onError={(e) => {
const target = e.currentTarget;
target.style.display = "none";
}}
/>
)}
<div
@@ -204,10 +200,6 @@ export function MascotChatbot({
src={mascotAvatar}
alt={mascotName}
className="w-6 h-6 rounded-full object-cover"
onError={(e) => {
const target = e.currentTarget;
target.style.display = "none";
}}
/>
<div className="bg-muted rounded-xl rounded-bl-none px-3 py-2">
<div className="flex gap-1">
@@ -38,7 +38,6 @@ export function MascotImage({
const sizeClass = sizeMap[size];
const chatSizeClass = chatSizeMap[size];
const [isVisible, setIsVisible] = useState(false);
const [imgError, setImgError] = useState(false);
useEffect(() => {
if (showChat && chatMessage) {
@@ -52,20 +51,13 @@ export function MascotImage({
return (
<div className="relative inline-block">
{imgError ? (
<div className={`flex items-center justify-center ${sizeClass} bg-muted/30 rounded-xl`}>
<MessageCircle className="h-6 w-6 text-muted-foreground/50" />
</div>
) : (
<motion.img
src="https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/apps/dimentorin/public/image/mascot-1.png"
alt="Mascot"
className={`object-contain drop-shadow-md ${sizeClass} ${className}`}
whileHover={{ scale: 1.05 }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
onError={() => setImgError(true)}
/>
)}
<motion.img
src="https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/apps/dimentorin/public/image/mascot-1.png"
alt="Mascot"
className={`object-contain drop-shadow-md ${sizeClass} ${className}`}
whileHover={{ scale: 1.05 }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
{/* Floating Chat Bubble */}
{isVisible && chatMessage && (
@@ -80,7 +72,7 @@ export function MascotImage({
{/* Chat bubble */}
<div className="bg-primary/90 text-primary-foreground rounded-xl px-4 py-2.5 shadow-lg backdrop-blur-sm border border-primary/30">
<div className="flex items-start gap-2">
<MessageCircle className="h-4 w-4 shrink-0 mt-0.5 text-primary-foreground/80" />
<MessageCircle className="h-4 w-4 shrink-0 mt-0.5 text-white/80" />
<p className="text-xs leading-relaxed font-medium line-clamp-3">
{chatMessage}
</p>
@@ -20,16 +20,11 @@ export function ParticleBackground() {
style={{ zIndex: -1 }}
>
{/* Top-right glow orb */}
<div className="absolute -top-40 -right-40 h-[500px] w-[500px] rounded-full blur-3xl animate-glow-pulse"
style={{ backgroundColor: "oklch(var(--particle-primary, 0.623 0.214 259.815 / 0.1))" }}
/>
<div className="absolute -top-40 -right-40 h-[500px] w-[500px] rounded-full bg-primary/10 blur-3xl animate-glow-pulse" />
{/* Bottom-left glow orb */}
<div
className="absolute -bottom-40 -left-40 h-[400px] w-[400px] rounded-full blur-3xl animate-glow-pulse"
style={{
backgroundColor: "oklch(var(--particle-secondary, 0.552 0.016 285.938 / 0.1))",
animationDelay: "1.5s",
}}
className="absolute -bottom-40 -left-40 h-[400px] w-[400px] rounded-full bg-blue-400/10 blur-3xl animate-glow-pulse"
style={{ animationDelay: "1.5s" }}
/>
</div>
);