refactor(mascot): replace ChibiMascot SVG with MascotImage PNG and simplify empty states

This commit is contained in:
MythEclipse
2026-06-03 13:56:11 +07:00
parent 2ea0ea5810
commit bb65178210
9 changed files with 54 additions and 22 deletions
@@ -1,7 +1,7 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useState } from "react"; import { useState } from "react";
import { cardItem, cardStagger } from "../../shared/hooks/useFramerStagger"; import { cardItem, cardStagger } from "../../shared/hooks/useFramerStagger";
import { EmptyStateMascot } from "../../widgets/mascot/ChibiMascot"; import { EmptyStateMascot } from "../../shared/ui";
import { ActivityChart } from "./components/ActivityChart"; import { ActivityChart } from "./components/ActivityChart";
import { ControlBar } from "./components/ControlBar"; import { ControlBar } from "./components/ControlBar";
import { Heatmap } from "./components/Heatmap"; import { Heatmap } from "./components/Heatmap";
@@ -53,12 +53,7 @@ export function AnalyticsPanel({ guildId, guildName }: AnalyticsPanelProps) {
} }
if (!guildId) { if (!guildId) {
return ( return <EmptyStateMascot />;
<EmptyStateMascot
variant="thinking"
message="Menunggu konfigurasi guild..."
/>
);
} }
return ( return (
@@ -10,8 +10,8 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
Input, Input,
MascotImage,
} from "../../shared/ui"; } from "../../shared/ui";
import { ChibiMascot } from "../../widgets/mascot/ChibiMascot";
interface AuthOverlayProps { interface AuthOverlayProps {
onAuthenticated: () => void; onAuthenticated: () => void;
@@ -50,7 +50,7 @@ export function AuthOverlay({ onAuthenticated }: AuthOverlayProps) {
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary"> <div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary">
<Lock className="h-6 w-6" /> <Lock className="h-6 w-6" />
</div> </div>
<ChibiMascot variant="peeking" size="sm" /> <MascotImage size="sm" />
</div> </div>
<CardTitle>Admin Access Required</CardTitle> <CardTitle>Admin Access Required</CardTitle>
<CardDescription> <CardDescription>
@@ -7,9 +7,7 @@ interface ActiveSpeakersProps {
export function ActiveSpeakers({ speakers }: ActiveSpeakersProps) { export function ActiveSpeakers({ speakers }: ActiveSpeakersProps) {
if (speakers.length === 0) { if (speakers.length === 0) {
return ( return <EmptyStateMascot />;
<EmptyStateMascot variant="sleeping" message="No active speakers~" />
);
} }
return ( return (
@@ -92,7 +92,7 @@ export function RecordingsSubPanel() {
} }
if (recordings.length === 0) { if (recordings.length === 0) {
return <EmptyStateMascot variant="sleeping" message="No recordings yet~" />; return <EmptyStateMascot />;
} }
return ( return (
@@ -1,6 +1,6 @@
import { parseMetadata } from "../../../entities/message/types"; import { parseMetadata } from "../../../entities/message/types";
import type { MessageRecord } from "../../../shared/api/client"; import type { MessageRecord } from "../../../shared/api/client";
import { EmptyStateMascot } from "../../../widgets/mascot/ChibiMascot"; import { EmptyStateMascot } from "../../../shared/ui";
interface ImageItem { interface ImageItem {
url: string; url: string;
@@ -68,7 +68,7 @@ export function ImageGrid({ messages }: { messages: MessageRecord[] }) {
} }
if (images.length === 0) { if (images.length === 0) {
return <EmptyStateMascot variant="thinking" message="No images yet~" />; return <EmptyStateMascot />;
} }
return ( return (
@@ -2,8 +2,7 @@ import { motion } from "framer-motion";
import { useEffect, useMemo, useRef } from "react"; import { useEffect, useMemo, useRef } from "react";
import type { MessageRecord } from "../../../shared/api/client"; import type { MessageRecord } from "../../../shared/api/client";
import { cardItem, cardStagger } from "../../../shared/hooks/useFramerStagger"; import { cardItem, cardStagger } from "../../../shared/hooks/useFramerStagger";
import { ScrollArea } from "../../../shared/ui"; import { ScrollArea, EmptyStateMascot } from "../../../shared/ui";
import { EmptyStateMascot } from "../../../widgets/mascot/ChibiMascot";
import { MessageCard, MessageCardSkeleton } from "./MessageCard"; import { MessageCard, MessageCardSkeleton } from "./MessageCard";
export interface MessageFeedProps { export interface MessageFeedProps {
@@ -84,7 +83,7 @@ export function MessageFeed({
} }
if (messages.length === 0) { if (messages.length === 0) {
return <EmptyStateMascot variant="waving" message={emptyText} />; return <EmptyStateMascot />;
} }
return ( return (
+1 -1
View File
@@ -1,6 +1,6 @@
// ─── Shared UI barrel export ──────────────────────────────────────────────── // ─── Shared UI barrel export ────────────────────────────────────────────────
export { EmptyStateMascot } from "../../widgets/mascot/ChibiMascot"; export { EmptyStateMascot, MascotImage } from "../../widgets/mascot/MascotImage";
export { Badge } from "./badge"; export { Badge } from "./badge";
export { Button } from "./button"; export { Button } from "./button";
export { export {
+3 -3
View File
@@ -2,7 +2,7 @@ import { motion } from "framer-motion";
import { BarChart3, MessageSquare, Radio } from "lucide-react"; import { BarChart3, MessageSquare, Radio } from "lucide-react";
import type { DashboardTab } from "../entities/ui/types"; import type { DashboardTab } from "../entities/ui/types";
import { cn } from "../shared/lib/utils"; import { cn } from "../shared/lib/utils";
import { ChibiMascot } from "./mascot/ChibiMascot"; import { MascotImage } from "./mascot/MascotImage";
const navItems: Array<{ id: DashboardTab; label: string; icon: typeof Radio }> = const navItems: Array<{ id: DashboardTab; label: string; icon: typeof Radio }> =
[ [
@@ -84,9 +84,9 @@ export function Sidebar({
{/* Spacer pushes mascot to bottom */} {/* Spacer pushes mascot to bottom */}
<div className="flex-1" /> <div className="flex-1" />
{/* Chibi mascot */} {/* Mascot PNG */}
<div className="flex justify-center pb-4"> <div className="flex justify-center pb-4">
<ChibiMascot size="sm" variant="idle" /> <MascotImage size="sm" />
</div> </div>
</motion.nav> </motion.nav>
); );
@@ -0,0 +1,40 @@
/**
* MascotImage — Anime mascot PNG from GitHub CDN
* Replaces ChibiMascot SVG component with external PNG asset
*/
interface MascotImageProps {
size?: "sm" | "md" | "lg";
className?: string;
}
const sizeMap = {
sm: "w-16 h-auto",
md: "w-32 h-auto",
lg: "w-48 h-auto",
};
export function MascotImage({ size = "md", className = "" }: MascotImageProps) {
const sizeClass = sizeMap[size];
return (
<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}`}
/>
);
}
/**
* EmptyStateMascot — Mascot for empty states
* Replaces ChibiMascot when showing empty data states
*/
export function EmptyStateMascot() {
return (
<div className="flex flex-col items-center justify-center gap-4 py-12">
<MascotImage size="md" className="opacity-60" />
<p className="text-sm text-muted-foreground">No data to display</p>
</div>
);
}