feat : update
CI / Verify (push) Successful in 1m14s
CI / Build immutable images and deploy (push) Successful in 4m51s

This commit is contained in:
2026-09-06 17:41:16 +07:00 Unverified
parent fcf6a2c187
commit 770c60c4a2
10 changed files with 91 additions and 17 deletions
+24 -6
View File
@@ -4,8 +4,15 @@ import { useEffect, useEffectEvent, useState } from "react";
import { PRESENCE_HEARTBEAT_MS } from "@/lib/presence-constants";
type VisitorCounts = { global: number; guide: number | null };
export type EditorsByGuide = Record<string, Array<{ userId: string; name: string }>>;
type VisitorCounts = {
global: number;
guide: number | null;
byGuide: Record<string, number>;
};
export type EditorsByGuide = Record<
string,
Array<{ userId: string; name: string; sections: string[] }>
>;
function tabId(storageKey: string): string {
const existing = window.sessionStorage.getItem(storageKey);
@@ -18,9 +25,11 @@ function tabId(storageKey: string): string {
function useHeartbeat<T>({
endpoint,
guideId,
section,
}: {
endpoint: string;
guideId: string | null;
section?: string | null;
}) {
const [value, setValue] = useState<T | null>(null);
const send = useEffectEvent(async (active = true) => {
@@ -28,7 +37,12 @@ function useHeartbeat<T>({
const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tabId: tabId(`buzz-sheet:${endpoint}:tab`), guideId, active }),
body: JSON.stringify({
tabId: tabId(`buzz-sheet:${endpoint}:tab`),
guideId,
...(section !== undefined ? { section } : {}),
active,
}),
cache: "no-store",
keepalive: !active,
});
@@ -56,7 +70,7 @@ function useHeartbeat<T>({
window.removeEventListener("pagehide", leave);
document.removeEventListener("visibilitychange", refresh);
};
}, [endpoint, guideId]);
}, [endpoint, guideId, section]);
return value;
}
@@ -65,6 +79,10 @@ export function useVisitorPresence(guideId: string | null) {
return useHeartbeat<VisitorCounts>({ endpoint: "/api/presence/visitors", guideId });
}
export function useAdminPresence(guideId: string | null) {
return useHeartbeat<{ editorsByGuide: EditorsByGuide }>({ endpoint: "/api/presence/admin", guideId });
export function useAdminPresence(guideId: string | null, section: string | null = null) {
return useHeartbeat<{ editorsByGuide: EditorsByGuide }>({
endpoint: "/api/presence/admin",
guideId,
section,
});
}