refactor(frontend): major rebuild — feature-sliced architecture + bug fixes + glass-morphism UI

Architecture:
- Feature-sliced directory: entities/, shared/, features/, widgets/
- Consolidated API client (shared/api/client.ts) — all endpoints in one file
- Single WebSocket manager (shared/ws/socket.ts) with typed event bus
- Extracted hooks: useAudioPlayback, useAudioTransmit, useLocalStorage, useUIState
- UI primitives moved to shared/ui/ with barrel export
- Added Skeleton, Toast, MobileTabBar components

Bug fixes (8/8):
1. useMemo→useEffect in RecordingsSubPanel (async side-effect anti-pattern)
2. ArrayBuffer.slice() before WebSocket send (shared buffer bug)
3. Proper useEffect dependency arrays throughout
4. Stable React keys (no index fallbacks)
5. onReanalyze properly awaited (Promise<void> return)
6. monitorGuild memoized with useMemo
7. localStorage validation with shape checking
8. Deleted duplicate socket logic (ws/client.ts removed)

UI polish:
- Glass-morphism design tokens (backdrop-blur, translucent cards)
- Gradient mesh background with subtle radial overlays
- Expandable sidebar + mobile bottom tab bar
- Skeleton loading placeholders
- Audio visualizer with CSS pulse animation

Deleted: src/api/, src/components/, src/hooks/, src/types/, src/ws/, src/lib/
Added: 40 new files across feature-sliced structure

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-01 16:42:36 +07:00
co-authored by Claude Opus 4.8
parent 1aab0d1df1
commit f5507e01f6
84 changed files with 2054 additions and 2658 deletions
+57
View File
@@ -0,0 +1,57 @@
import { BarChart3, MessageSquare, Radio } from "lucide-react";
import type { DashboardTab } from "../entities/ui/types";
import { cn } from "../shared/lib/utils";
import { Button } from "../shared/ui";
const navItems: Array<{ id: DashboardTab; label: string; icon: typeof Radio }> = [
{ id: "live", label: "Live", icon: Radio },
{ id: "messages", label: "Messages", icon: MessageSquare },
{ id: "analytics", label: "Analytics", icon: BarChart3 },
];
interface SidebarProps {
activeTab: DashboardTab;
onTabChange: (tab: DashboardTab) => void;
collapsed?: boolean;
}
export function Sidebar({ activeTab, onTabChange, collapsed }: SidebarProps) {
return (
<aside
className={cn(
"hidden shrink-0 border-r border-border bg-card/60 p-5 backdrop-blur transition-all duration-300 md:block",
collapsed ? "w-16" : "w-64",
)}
>
<div className={cn("mb-8 flex items-center gap-3", collapsed && "justify-center")}>
{!collapsed && (
<div>
<div className="flex items-baseline gap-2">
<img src="/logo.svg" alt="GMW" className="h-10 w-10 rounded-2xl" />
<span className="font-bold tracking-tight text-primary text-lg">GMW</span>
</div>
<div className="text-xs text-muted-foreground">Discord Moderation Watcher</div>
</div>
)}
{collapsed && <img src="/logo.svg" alt="GMW" className="h-9 w-9 rounded-xl" />}
</div>
<nav className="space-y-2">
{navItems.map((item) => {
const Icon = item.icon;
return (
<Button
key={item.id}
variant={activeTab === item.id ? "secondary" : "ghost"}
className={cn("w-full justify-start", activeTab === item.id && "bg-primary/15 text-primary", collapsed && "justify-center px-0")}
onClick={() => onTabChange(item.id)}
title={collapsed ? item.label : undefined}
>
<Icon className="h-4 w-4" />
{!collapsed && item.label}
</Button>
);
})}
</nav>
</aside>
);
}