feat(frontend): rebuild as SSR with server-authoritative shared state
Rombak total alur data frontend: dari static-export CSR (tiap browser fetch sendiri + akumulasi state voice per-tab) jadi server-side rendering. Frontend (Next.js): - next.config: output export -> standalone; halaman jadi server components - server data layer baru src/lib/api/server.ts (GMW_BACKEND_URL, no window) - dashboard/media/messages/moderation/recordings/voice page -> RSC yang fetch backend di render-time, seed ke client view (SWR fallbackData) - hook-hook utama terima initialData -> first paint data server, revalidate SWR setelahnya, tanpa spinner-blank-load - messages: guild/channel/tab/selected dibaca dari URL di server, page awal di-fetch server-side Shared realtime state (voice) server-authoritative: - backend src/modules/voice/live-speaker.ts: agregat voice_active_user dari gateway jadi snapshot authoritatif (single source of truth semua browser) - GET /api/voice/status kini include activeSpeakers - WS initial states kirim voice_state snapshot saat connect (late join langsung dapat state yang sama, bukan daftar kosong) - useSpeakers seed dari server snapshot + voice_state full-replace + voice_active_user delta upsert Deploy: - flake.nix: frontend package build SSR standalone (server.js wrapper, GMW_FRONTEND_PORT=4017); proxy nginx template proxy / -> Next server, /api + /ws tetap ke backend :4001
This commit is contained in:
1 parent
aa440eda69
commit
f20889868d
32 files changed
+1556
-956
No files matched your search
@@ -1,20 +1,35 @@
|
||||
import useSWR from "swr";
|
||||
import { moderationApi } from "@/lib/api";
|
||||
import type { ModerationStats } from "@/lib/types";
|
||||
import type { ModerationAction, ModerationStats } from "@/lib/types";
|
||||
|
||||
export function useModerationStats() {
|
||||
return useSWR<ModerationStats>(["moderation-stats"], () =>
|
||||
moderationApi.getStats(),
|
||||
export function useModerationStats(initialData?: ModerationStats) {
|
||||
return useSWR<ModerationStats>(
|
||||
["moderation-stats"],
|
||||
() => moderationApi.getStats(),
|
||||
{ fallbackData: initialData },
|
||||
);
|
||||
}
|
||||
|
||||
export function useModerationActions(status?: string, actionType?: string) {
|
||||
export function useModerationActions(
|
||||
status?: string,
|
||||
actionType?: string,
|
||||
initialData?: ModerationAction[],
|
||||
) {
|
||||
const key = [
|
||||
"moderation-actions",
|
||||
status ?? "__all__",
|
||||
actionType ?? "__all__",
|
||||
];
|
||||
return useSWR(
|
||||
["moderation-actions", status ?? "__all__", actionType ?? "__all__"],
|
||||
key,
|
||||
async () => {
|
||||
const res = await moderationApi.listActions(100, status, actionType);
|
||||
return res.data;
|
||||
},
|
||||
{ keepPreviousData: true },
|
||||
{
|
||||
keepPreviousData: true,
|
||||
fallbackData:
|
||||
!status && !actionType && initialData ? initialData : undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user