EventBuzz_Website/src/hooks/useSessionQuestionsRtdb.ts

134 lines
3.7 KiB
TypeScript

import { useEffect, useState } from "react";
import {
subscribeSessionQuestions,
type RtdbQuestionItem,
} from "../lib/sessionQuestionsRtdb";
import { getVisitorSessionQuestions, type VisitorSessionQuestion } from "../lib/api";
import { isFirebaseClientConfigured } from "../lib/firebaseClient";
const API_POLL_MS = 4000;
function isFirebasePermissionError(message: string): boolean {
return /permission_denied|PERMISSION_DENIED|permission denied/i.test(message);
}
function apiQuestionsToRtdbItems(rows: VisitorSessionQuestion[]): RtdbQuestionItem[] {
return rows
.map((q) => ({
_id: String(q._id),
author: String(q.author ?? "Anonymous"),
text: String(q.text ?? ""),
up: typeof q.up === "number" ? q.up : Number(q.up) || 0,
down: typeof q.down === "number" ? q.down : Number(q.down) || 0,
createdAt: q.createdAt != null ? String(q.createdAt) : undefined,
status: "pending",
audienceListed: true,
}))
.sort((a, b) => {
if (b.up !== a.up) return b.up - a.up;
const ta = a.createdAt ? Date.parse(a.createdAt) : 0;
const tb = b.createdAt ? Date.parse(b.createdAt) : 0;
return tb - ta;
});
}
/**
* Live session questions for attendees.
* - Tries Firebase RTDB when configured.
* - On `permission_denied` (rules require auth), falls back to GET /api/visitor/sessions/:id/questions with polling.
*/
export function useSessionQuestionsRtdb(
sessionId: string | null | undefined,
eventId?: string | null,
) {
const [items, setItems] = useState<RtdbQuestionItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const sid = sessionId?.trim() ?? "";
const eid = eventId?.trim() ?? "";
useEffect(() => {
if (!sid || !eid) {
setItems([]);
setLoading(false);
setError(
sid && !eid
? "event_id is required to load questions."
: null,
);
return;
}
let cancelled = false;
let pollTimer: ReturnType<typeof setInterval> | null = null;
let unsubRtdb: (() => void) | null = null;
let usingApi = false;
const loadViaApi = async () => {
try {
const rows = await getVisitorSessionQuestions(sid, eid);
if (cancelled) return;
setItems(apiQuestionsToRtdbItems(rows));
setLoading(false);
setError(null);
} catch (e) {
if (cancelled) return;
setError(e instanceof Error ? e.message : "Could not load questions.");
setItems([]);
setLoading(false);
}
};
const startApiPolling = () => {
if (usingApi) return;
usingApi = true;
if (unsubRtdb) {
unsubRtdb();
unsubRtdb = null;
}
void loadViaApi();
pollTimer = setInterval(() => {
if (cancelled || document.visibilityState !== "visible") return;
void loadViaApi();
}, API_POLL_MS);
};
setLoading(true);
setError(null);
if (!isFirebaseClientConfigured()) {
startApiPolling();
} else {
unsubRtdb = subscribeSessionQuestions(
sid,
(list) => {
if (cancelled || usingApi) return;
setItems(list);
setLoading(false);
setError(null);
},
(err) => {
if (cancelled) return;
const msg = err.message || String(err);
if (isFirebasePermissionError(msg)) {
startApiPolling();
return;
}
setError(msg);
setItems([]);
setLoading(false);
},
);
}
return () => {
cancelled = true;
if (unsubRtdb) unsubRtdb();
if (pollTimer) clearInterval(pollTimer);
};
}, [sid, eid]);
return { items, loading, error };
}