"use client"; import React, { useState, useEffect, useCallback } from "react"; import Link from "next/link"; import { Lock, Eye, EyeOff, Calendar, Clock, Sparkles, ExternalLink, Download, RefreshCw, AlertTriangle, ShieldAlert, CheckCircle2, XCircle, BarChart3, Layers, LogOut, ArrowLeft, Cpu, Image as ImageIcon, Activity, Filter, } from "lucide-react"; import { StatsSummary } from "@/lib/storage"; export default function PortalPage() { const [isAuthenticated, setIsAuthenticated] = useState(null); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [authError, setAuthError] = useState(""); const [isLoggingIn, setIsLoggingIn] = useState(false); // Filter State const [period, setPeriod] = useState<"day" | "week" | "month" | "all" | "custom">("week"); const [fromDate, setFromDate] = useState(""); const [toDate, setToDate] = useState(""); const [selectedStyleFilter, setSelectedStyleFilter] = useState("all"); // Dashboard Data State const [stats, setStats] = useState(null); const [isLoading, setIsLoading] = useState(false); const [activeTab, setActiveTab] = useState<"gallery" | "errors">("gallery"); // Check initial authentication const checkAuth = useCallback(async () => { try { const res = await fetch("/api/portal/auth"); if (res.ok) { setIsAuthenticated(true); } else { setIsAuthenticated(false); } } catch { setIsAuthenticated(false); } }, []); useEffect(() => { checkAuth(); }, [checkAuth]); // Fetch statistics const fetchStats = useCallback(async () => { if (!isAuthenticated) return; setIsLoading(true); try { let url = `/api/portal/stats?period=${period}`; if (period === "custom") { if (fromDate) url += `&from=${fromDate}`; if (toDate) url += `&to=${toDate}`; } const res = await fetch(url); if (res.status === 401) { setIsAuthenticated(false); return; } const data = await res.json(); if (data.success && data.stats) { setStats(data.stats); } } catch (err) { console.error("Failed to fetch stats:", err); } finally { setIsLoading(false); } }, [isAuthenticated, period, fromDate, toDate]); useEffect(() => { if (isAuthenticated) { fetchStats(); } }, [isAuthenticated, fetchStats]); // Handle Login const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setAuthError(""); setIsLoggingIn(true); try { const res = await fetch("/api/portal/auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password }), }); const data = await res.json(); if (res.ok && data.success) { setIsAuthenticated(true); setPassword(""); } else { setAuthError(data.error || "Incorrect password"); } } catch { setAuthError("Network error. Please try again."); } finally { setIsLoggingIn(false); } }; // Handle Logout const handleLogout = async () => { await fetch("/api/portal/auth", { method: "DELETE" }); setIsAuthenticated(false); setStats(null); }; // Filter gallery images by selected style dropdown const displayedImages = (stats?.images || []).filter((img) => { if (selectedStyleFilter === "all") return true; return img.styleId === selectedStyleFilter; }); // Loading Initial Auth State if (isAuthenticated === null) { return (
Authenticating...
); } // Login Screen if (!isAuthenticated) { return (

万圣空手 · 管理后台

上海健志空手道馆 · Halloween Management & Gallery

setPassword(e.target.value)} autoFocus className="w-full px-4 py-3.5 bg-[#0e071c] border border-purple-800/50 rounded-2xl text-white placeholder-purple-400/40 focus:outline-none focus:border-orange-500 focus:ring-2 focus:ring-orange-500/20 text-sm font-mono pr-12 transition-all" />
{authError && (
{authError}
)}
Back to Photo Booth
); } // Main Portal Dashboard return (
{/* Top Navigation Bar */}

万圣空手道馆 · Spooky Hand Portal

上海健志空手道馆 · Live statistics, error tracking & gallery

Photo Booth
{/* Main Container */}
{/* Period Filter Bar */}
Time Period:
{( [ { id: "day", label: "Today" }, { id: "week", label: "Last 7 Days" }, { id: "month", label: "Last 30 Days" }, { id: "all", label: "All Time" }, { id: "custom", label: "Custom Range" }, ] as const ).map((item) => ( ))}
{/* Custom Date Pickers */} {period === "custom" && (
setFromDate(e.target.value)} className="bg-[#0e071c] border border-purple-800/60 rounded-xl px-3 py-1.5 text-xs text-white focus:outline-none focus:border-orange-500" /> to setToDate(e.target.value)} className="bg-[#0e071c] border border-purple-800/60 rounded-xl px-3 py-1.5 text-xs text-white focus:outline-none focus:border-orange-500" />
)}
{/* KPI Summary Cards */}
{/* Card 1: Total Generations */}
Total Generations
{stats?.totalAttempts ?? 0}
{stats?.successCount ?? 0} success • {stats?.successRate ?? 0}% rate
{/* Card 2: Average Generation Speed */}
Avg Latency
{stats?.avgDurationMs ? `${(stats.avgDurationMs / 1000).toFixed(1)}s` : "0s"}
End-to-end transformation time
{/* Card 3: Safety Refusals */}
Content Refusals
{stats?.refusedCount ?? 0}
Filtered by SFW / safety guardrails
{/* Card 4: Server / Provider Errors */}
Server Errors
{stats?.errorCount ?? 0}
API timeouts & network failures
{/* Analytics Breakdown Row: Daily Trend & Disguise Popularity */}
{/* Daily Trend Histogram */}

Generations by Day

{stats?.dailyTrends?.length || 0} days shown
{stats?.dailyTrends && stats.dailyTrends.length > 0 ? (
{(() => { const maxCount = Math.max( 1, ...stats.dailyTrends.map((d) => d.total) ); return stats.dailyTrends.map((day) => { const heightPercent = Math.max( 8, Math.round((day.total / maxCount) * 100) ); const formattedDate = day.date.slice(5); // MM-DD return (
{/* Count (always visible, never clipped) */} {day.total} {/* Bar */}
{/* Label */} {formattedDate}
); }); })()}
) : (
No generations recorded for this time range yet.
)}
{/* Popular Disguise Breakdown */}

Top Disguise Styles

{stats?.styleBreakdown && stats.styleBreakdown.length > 0 ? ( stats.styleBreakdown.map((style) => (
{style.styleName} {style.count} ({style.percentage}%)
)) ) : (
No styles used in this period.
)}
{/* Provider Health Chips */}
{stats?.providerBreakdown?.map((p) => ( {p.provider}: {p.count} ))}
{/* View Tabs: Gallery vs Errors */}
{/* Style Filter for Gallery */} {activeTab === "gallery" && (
)}
{/* TAB 1: Generated Images Gallery */} {activeTab === "gallery" && (
{displayedImages.length > 0 ? (
{displayedImages.map((img) => ( ))}
) : (

No generated portraits found for this selection.

Snap photos in the booth to see them appear here live!

)}
)} {/* TAB 2: Error & Content Refusal Logs */} {activeTab === "errors" && (
{stats?.errorLogs && stats.errorLogs.length > 0 ? (
{stats.errorLogs.map((log) => { const isSafety = log.errorType === "safety_refusal" || log.status === "refused"; return ( ); })}
Time Type Disguise Provider / Model Reason / Error Message Client ID
{new Date(log.timestamp).toLocaleString("en-CA", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", })} {isSafety ? "Safety Refusal" : "Server / API Error"} {log.styleName} {log.provider} ({log.model}) {log.refusalReason || log.errorMessage || "Unknown error"} {log.clientId || "127.0.0.1"}
) : (

No errors or content refusals recorded!

Your photo booth is running smoothly.

)}
)}
); }