refactor(mascot): remove useMascotSummary integration from sidebar

This commit is contained in:
MythEclipse
2026-06-03 18:29:54 +07:00
parent b5a218daa1
commit 01eaf7275b
2 changed files with 106 additions and 108 deletions
@@ -3,7 +3,6 @@ import type { ReactNode } from "react";
import type { DashboardTab } from "../entities/ui/types";
import type { MessageRecord, VoiceStatus } from "../shared/api/client";
import { fadeSlideUp } from "../shared/hooks/useFramerStagger";
import { useMascotSummary } from "../shared/hooks/useMascotSummary";
import type { WsStatus } from "../shared/ws/socket";
import { Header } from "./Header";
import { ParticleBackground } from "./particles/ParticleBackground";
@@ -30,11 +29,6 @@ export function DashboardLayout({
guildId,
channelId,
}: DashboardLayoutProps) {
// Generate mascot summary from recent messages
const mascotSummary = useMascotSummary({
messages: recentMessages,
enabled: activeTab === "messages" && recentMessages.length > 0,
});
return (
<div className="relative min-h-screen bg-background text-foreground">
{/* Sakura particle layer */}
@@ -44,7 +38,6 @@ export function DashboardLayout({
<Sidebar
activeTab={activeTab}
onTabChange={onTabChange}
mascotChatMessage={mascotSummary}
recentMessages={recentMessages}
guildId={guildId}
channelId={channelId}
+106 -101
View File
@@ -18,7 +18,6 @@ interface SidebarProps {
activeTab: DashboardTab;
onTabChange: (tab: DashboardTab) => void;
collapsed?: boolean;
mascotChatMessage?: string;
recentMessages?: MessageRecord[];
guildId?: string;
channelId?: string;
@@ -28,14 +27,15 @@ export function Sidebar({
activeTab,
onTabChange,
collapsed = true,
mascotChatMessage = "",
recentMessages = [],
guildId,
channelId,
}: SidebarProps) {
const mascotChat = useMascotChat({
messageCount: recentMessages.length,
activeParticipants: new Set(recentMessages.map((message) => message.user_id)).size,
activeParticipants: new Set(
recentMessages.map((message) => message.user_id),
).size,
lastActivity: recentMessages.length > 0 ? "Active" : "Idle",
topicsDiscussed: ["Messages", "Moderation", "Analytics"],
guildId,
@@ -43,108 +43,113 @@ export function Sidebar({
});
return (
<>
<motion.nav
className={cn(
"relative hidden shrink-0 flex-col overflow-visible border-r border-[#7EC8E3]/20 bg-white/70 backdrop-blur-md transition-all duration-300 md:flex",
collapsed ? "w-16" : "w-64",
)}
layout
transition={{ type: "spring", stiffness: 300, damping: 30 }}
>
{/* App icon only — no branding text */}
<div
<motion.nav
className={cn(
"flex items-center py-5",
collapsed ? "justify-center" : "flex-col px-4",
"relative hidden shrink-0 flex-col overflow-visible border-r border-[#7EC8E3]/20 bg-white/70 backdrop-blur-md transition-all duration-300 md:flex",
collapsed ? "w-16" : "w-64",
)}
layout
transition={{ type: "spring", stiffness: 300, damping: 30 }}
>
<img src="https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/docs/logo.svg" alt="GMW" className="h-8 w-8 rounded-xl" />
{/* Mascot image — only when expanded */}
{!collapsed && (
<img
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"
/>
)}
</div>
{/* Navigation items */}
<div className="flex flex-col gap-1 px-2">
{navItems.map((item) => {
const Icon = item.icon;
const isActive = activeTab === item.id;
return (
<button
key={item.id}
onClick={() => onTabChange(item.id)}
title={collapsed ? item.label : undefined}
className={cn(
"group relative flex items-center rounded-xl p-2.5 text-sm font-medium transition-all duration-200 ease-out",
collapsed ? "justify-center" : "gap-3",
isActive
? "bg-primary-soft text-primary ring-2 ring-primary/20"
: "text-muted-foreground hover:bg-primary-soft/40 hover:text-primary/80",
)}
onMouseEnter={(e) => {
e.currentTarget.style.transform = "translateX(2px) scale(1.1)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "translateX(0) scale(1)";
}}
>
<Icon className="h-4 w-4 shrink-0" />
{!collapsed && <span>{item.label}</span>}
</button>
);
})}
</div>
{/* Spacer pushes mascot to bottom */}
<div className="flex-1" />
{/* Mascot PNG with anchored chatbot */}
<div className="relative flex justify-center pb-4">
{!mascotChat.isOpen && (
<motion.div
initial={{ opacity: 0, x: -8, y: 6, scale: 0.96 }}
animate={{ opacity: 1, x: 0, y: 0, scale: 1 }}
transition={{ type: "spring", stiffness: 280, damping: 24 }}
className="pointer-events-none absolute bottom-20 left-12 z-40 w-64"
>
<div className="relative rounded-2xl border border-sky-200/80 bg-white/95 px-4 py-3 text-left shadow-xl shadow-sky-100/70 backdrop-blur-md">
<p className="text-[11px] font-semibold uppercase tracking-wide text-primary/70">
Discord Watcher
</p>
<p className="mt-1 text-xs leading-relaxed text-slate-700">
{mascotChatMessage ||
"Halo! Saya mascot mu. Klik aku kalau mau tanya soal obrolan atau moderasi ✨"}
</p>
{/* Outer: border layer — 1px larger, extends left & down to show border on diagonal/bottom edges */}
<div className="absolute -left-[13px] bottom-[7px] z-10 h-[21px] w-[21px] bg-sky-200/80 [clip-path:polygon(100%_0,0_100%,100%_70%)]" />
{/* Inner: fill layer — original position & size, matches bubble bg */}
<div className="absolute -left-3 bottom-2 z-20 h-5 w-5 bg-white/95 [clip-path:polygon(100%_0,0_100%,100%_70%)]" />
</div>
</motion.div>
)}
<button
type="button"
onClick={() => mascotChat.setIsOpen(!mascotChat.isOpen)}
className="relative z-50 rounded-2xl p-1 transition-transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary/40"
title="Chat dengan mascot"
{/* App icon only — no branding text */}
<div
className={cn(
"flex items-center py-5",
collapsed ? "justify-center" : "flex-col px-4",
)}
>
<MascotImage size="sm" />
</button>
</div>
</motion.nav>
<MascotChatbot
isOpen={mascotChat.isOpen}
onClose={() => mascotChat.setIsOpen(false)}
onSendMessage={mascotChat.handleSendMessage}
mascotName="Discord Watcher"
className="fixed bottom-20 left-[80px] z-[9999]"
/>
<img
src="https://raw.githubusercontent.com/IMPHNEN/imphnen-frontend-service/develop/docs/logo.svg"
alt="GMW"
className="h-8 w-8 rounded-xl"
/>
{/* Mascot image — only when expanded */}
{!collapsed && (
<img
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"
/>
)}
</div>
{/* Navigation items */}
<div className="flex flex-col gap-1 px-2">
{navItems.map((item) => {
const Icon = item.icon;
const isActive = activeTab === item.id;
return (
<button
key={item.id}
onClick={() => onTabChange(item.id)}
title={collapsed ? item.label : undefined}
className={cn(
"group relative flex items-center rounded-xl p-2.5 text-sm font-medium transition-all duration-200 ease-out",
collapsed ? "justify-center" : "gap-3",
isActive
? "bg-primary-soft text-primary ring-2 ring-primary/20"
: "text-muted-foreground hover:bg-primary-soft/40 hover:text-primary/80",
)}
onMouseEnter={(e) => {
e.currentTarget.style.transform =
"translateX(2px) scale(1.1)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "translateX(0) scale(1)";
}}
>
<Icon className="h-4 w-4 shrink-0" />
{!collapsed && <span>{item.label}</span>}
</button>
);
})}
</div>
{/* Spacer pushes mascot to bottom */}
<div className="flex-1" />
{/* Mascot PNG with anchored chatbot */}
<div className="relative flex justify-center pb-4">
{!mascotChat.isOpen && (
<motion.div
initial={{ opacity: 0, x: -8, y: 6, scale: 0.96 }}
animate={{ opacity: 1, x: 0, y: 0, scale: 1 }}
transition={{ type: "spring", stiffness: 280, damping: 24 }}
className="pointer-events-none absolute -top-28 left-12 z-40 w-64"
>
<div className="relative rounded-2xl border border-sky-200/80 bg-white/95 px-4 py-3 text-left shadow-xl shadow-sky-100/70 backdrop-blur-md">
<p className="text-[11px] font-semibold uppercase tracking-wide text-primary/70">
Discord Watcher
</p>
<p className="mt-1 text-xs leading-relaxed text-slate-700">
Halo! Saya mascot mu. Klik aku kalau mau tanya soal obrolan
atau moderasi
</p>
{/* Outer: border layer — 1px larger, extends left & down to show border on diagonal/bottom edges */}
<div className="absolute -left-[13px] bottom-[7px] z-10 h-[21px] w-[21px] bg-sky-200/80 [clip-path:polygon(100%_0,0_100%,100%_70%)]" />
{/* Inner: fill layer — original position & size, matches bubble bg */}
<div className="absolute -left-3 bottom-2 z-20 h-5 w-5 bg-white/95 [clip-path:polygon(100%_0,0_100%,100%_70%)]" />
</div>
</motion.div>
)}
<button
type="button"
onClick={() => mascotChat.setIsOpen(!mascotChat.isOpen)}
className="relative z-50 rounded-2xl p-1 transition-transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary/40"
title="Chat dengan mascot"
>
<MascotImage size="sm" />
</button>
</div>
</motion.nav>
<MascotChatbot
isOpen={mascotChat.isOpen}
onClose={() => mascotChat.setIsOpen(false)}
onSendMessage={mascotChat.handleSendMessage}
mascotName="Discord Watcher"
className="fixed bottom-[170px] left-[80px] z-[9999]"
/>
</>
);
}