Deploy to VPS / deploy (push) Failing after 42s
Complete migration from services/frontend.old/ (Leptos 0.7 WASM + Rust) to services/frontend/ (Next.js 16 static export + TypeScript + Tailwind v4). Summary: - Port all shared types (message, guild, voice, media, dashboard, recording, ui) - Build fetch-based API client covering all 30+ backend endpoints - WebSocket client with auto-reconnect (exponential backoff, 20 attempts) - React context provider for WS with typed event subscription (22 event types) - Login page with localStorage auth + auto-redirect - Dashboard layout with sidebar, header (WS status + theme toggle) - Messages: feed, search, images tab, review tab, channel filter, detail modal - Live: voice connection, music player, recordings, mic transmit, active speakers - Dashboard: stats, user list, channel list, detail views - Mascot chatbot with history + clear - uiStateApi persistence for selected tab - Add static export config, update deploy scripts and CI
110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import { Suspense, useEffect, useRef } from "react";
|
|
import { uiStateApi } from "@/lib/api";
|
|
import { Header } from "@/components/layout/header";
|
|
import { MobileTabBar } from "@/components/layout/mobile-tab-bar";
|
|
import { Sidebar } from "@/components/layout/sidebar";
|
|
import { MascotChatbot } from "@/features/mascot/mascot-chatbot";
|
|
import { AuthProvider, useAuth } from "@/lib/hooks/use-auth";
|
|
import { WsProvider } from "@/lib/ws/context";
|
|
|
|
function DashboardGuard({ children }: { children: React.ReactNode }) {
|
|
const { authenticated, loading } = useAuth();
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
if (!loading && !authenticated) {
|
|
router.push("/");
|
|
}
|
|
}, [authenticated, loading, router]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center">
|
|
<div className="size-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!authenticated) return null;
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function DashboardShell({ children }: { children: React.ReactNode }) {
|
|
const searchParams = useSearchParams();
|
|
const router = useRouter();
|
|
const restored = useRef(false);
|
|
|
|
const activeTab = (searchParams.get("tab") ?? "messages") as
|
|
| "messages"
|
|
| "live"
|
|
| "dashboard";
|
|
|
|
// Restore persisted tab on mount (only if no explicit tab in URL)
|
|
useEffect(() => {
|
|
if (restored.current) return;
|
|
const tabParam = searchParams.get("tab");
|
|
if (tabParam) {
|
|
restored.current = true;
|
|
return; // explicit tab in URL — don't override
|
|
}
|
|
uiStateApi
|
|
.get()
|
|
.then((state) => {
|
|
restored.current = true;
|
|
const savedTab = state.active_tab;
|
|
if (savedTab && savedTab !== activeTab) {
|
|
router.replace(`/dashboard?tab=${savedTab}`);
|
|
}
|
|
})
|
|
.catch(() => {
|
|
restored.current = true;
|
|
});
|
|
}, [searchParams, activeTab, router]);
|
|
|
|
// Persist tab changes
|
|
useEffect(() => {
|
|
if (!restored.current) return;
|
|
uiStateApi.save({ active_tab: activeTab }).catch(() => {});
|
|
}, [activeTab]);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<Sidebar activeTab={activeTab} />
|
|
<div className="md:pl-56 flex flex-col min-h-screen">
|
|
<Header />
|
|
<main className="flex-1 p-4 md:p-6 pb-20 md:pb-6">{children}</main>
|
|
</div>
|
|
<MobileTabBar activeTab={activeTab} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function DashboardLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<AuthProvider>
|
|
<DashboardGuard>
|
|
<WsProvider>
|
|
<Suspense
|
|
fallback={
|
|
<div className="flex min-h-screen items-center justify-center">
|
|
<div className="size-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
|
</div>
|
|
}
|
|
>
|
|
<DashboardShell>{children}</DashboardShell>
|
|
</Suspense>
|
|
<MascotChatbot />
|
|
</WsProvider>
|
|
</DashboardGuard>
|
|
</AuthProvider>
|
|
);
|
|
}
|