feat: migrate frontend to Astro SSG with design system

- Replace monolithic React SPA (App.tsx) with 7 Astro pages (+ routing)
- Implement full design system from design/ (OKLCH tokens, Tailwind 4 @theme)
- Add 12 Astro components (Button, Badge, Card, Sidebar, Header, states)
- Add 11 React islands (AuthGuard, MessageFeed, VoiceControls, AudioVisualizer, etc.)
- Add 3 Zustand stores (UI, voice, message state)
- Add CSS architecture: dark/light themes, glassmorphism, fluid typography, animations
- Add 404, login, settings, recordings pages
- Remove 40+ legacy React SPA files
- Add Particles background and MascotChat deferred islands
- Wire WebSocket bridge for real-time messages and voice events

Build: 7 pages in ~900ms
Typecheck: clean (0 errors)
Lint: clean (0 errors)
This commit is contained in:
asepharyana
2026-07-02 01:18:45 +07:00
parent 5e40b1ad8c
commit 8ad888da28
110 changed files with 3396 additions and 4980 deletions
+42 -2
View File
@@ -6,9 +6,9 @@ import type {
VoiceRecordingUploadData,
} from "@bete/shared";
import { useCallback, useEffect, useRef, useState } from "react";
import type { MediaState } from "../../entities/media/types.js";
import { createLogger } from "../lib/logger.js";
import { getSessionToken } from "../api/client.js";
import { createLogger } from "../lib/logger.js";
import type { MediaState } from "../types/media.js";
import type { ActiveSpeakerData } from "./events.js";
const logger = createLogger("socket");
@@ -391,3 +391,43 @@ export function useDashboardSocket(handlers: WsHandlers) {
return { status, send, socketRef: { current: _wsInstance } };
}
/**
* Singleton manager for the WebSocket connection.
* Provides imperative connect/disconnect/send access alongside useDashboardSocket.
*/
export class SocketManager {
private static _instance: SocketManager;
private constructor() {}
static getInstance(): SocketManager {
if (!SocketManager._instance) {
SocketManager._instance = new SocketManager();
}
return SocketManager._instance;
}
/** Ensure the WebSocket is connected (reconnects if closed). */
connect(): void {
_closed = false;
ensureConnected();
}
/** Close the WebSocket and stop reconnection. */
disconnect(): void {
_closed = true;
if (_reconnectTimer) clearTimeout(_reconnectTimer);
if (_wsInstance) {
_wsInstance.close();
_wsInstance = null;
}
}
/** Send data through the WebSocket (no-op if not connected). */
send(data: ArrayBuffer | string): void {
if (_wsInstance?.readyState === WebSocket.OPEN) {
_wsInstance.send(data);
}
}
}