import { useEffect, Suspense } from "react"; import { NavLink, useLocation, useBlocker, Outlet, useNavigate } from "react-router-dom"; import Sidebar from "./Sidebar"; import { useVideoGen } from "../stores/videoGenStore"; import { useDirty } from "../stores/dirtyStore"; import { useAuth } from "../stores/authStore"; // Fallback nhẹ khi chunk trang đang tải (code-splitting). function PageFallback() { return (
Đang tải công cụ…
); } const tabs = [ { to: "/", label: "Trang chủ" }, { to: "/huong-dan", label: "Hướng dẫn" }, { to: "/ho-tro", label: "Hỗ trợ" }, ]; export default function Layout() { const location = useLocation(); const navigate = useNavigate(); const { authorized, user, logout } = useAuth(); useEffect(() => { if (!authorized) navigate("/dang-nhap", { replace: true }); }, [authorized]); const handleLogout = () => { logout(); navigate("/dang-nhap"); }; const isVideoGen = location.pathname === "/tao-video"; const { connected, url } = useVideoGen(); const { dirty, message } = useDirty(); const blocker = useBlocker( ({ currentLocation, nextLocation }) => dirty && currentLocation.pathname !== nextLocation.pathname ); useEffect(() => { if (blocker.state === "blocked") { const proceed = window.confirm( message || "Bạn có chắc muốn rời khỏi công cụ này? Tiến trình hiện tại sẽ bị mất." ); if (proceed) { blocker.proceed(); } else { blocker.reset(); } } }, [blocker.state, message]); return (
{tabs.map((t) => ( "tab" + (isActive ? " active" : "")}> {t.label} ))}
{user}
TNT
{/* Chỉ MỘT được render tại một thời điểm (cái còn lại là `false`) -> trang route chỉ mount 1 lần, không còn chạy đôi effect/poll/upload. iframe Gradio LUÔN nằm trong cây (ngoài điều kiện) để giữ phiên kết nối khi chuyển tool rồi quay lại /tao-video. */}
}>{!isVideoGen && }
}>{isVideoGen && }