Files
fotospiel-app/resources/js/guest/polling/usePollStats.ts
Codex Agent 64a5411fb9 - Reworked the tenant admin login page
- Updated the User model to implement Filament’s tenancy contracts
- Seeded a ready-to-use demo tenant (user, tenant, active package, purchase)
- Introduced a branded, translated 403 error page to replace the generic forbidden message for unauthorised admin hits
- Removed the public “Register” links from the marketing header
- hardened join event logic and improved error handling in the guest pwa.
2025-10-13 12:50:46 +02:00

74 lines
2.1 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
export type EventStats = {
onlineGuests: number;
tasksSolved: number;
latestPhotoAt: string | null;
};
type StatsResponse = {
online_guests?: number;
tasks_solved?: number;
latest_photo_at?: string;
};
export function usePollStats(eventKey: string | null | undefined) {
const [data, setData] = useState<EventStats>({ onlineGuests: 0, tasksSolved: 0, latestPhotoAt: null });
const [loading, setLoading] = useState(true);
const timer = useRef<number | null>(null);
const [visible, setVisible] = useState(
typeof document !== 'undefined' ? document.visibilityState === 'visible' : true
);
const canPoll = Boolean(eventKey);
async function fetchOnce(activeKey: string) {
try {
const res = await fetch(`/api/v1/events/${encodeURIComponent(activeKey)}/stats`, {
headers: { 'Cache-Control': 'no-store' },
});
if (res.status === 304) return;
if (!res.ok) {
if (res.status === 404) {
setData({ onlineGuests: 0, tasksSolved: 0, latestPhotoAt: null });
}
return;
}
const json: StatsResponse = await res.json();
setData({
onlineGuests: json.online_guests ?? 0,
tasksSolved: json.tasks_solved ?? 0,
latestPhotoAt: json.latest_photo_at ?? null,
});
} finally {
setLoading(false);
}
}
useEffect(() => {
const onVis = () => setVisible(document.visibilityState === 'visible');
document.addEventListener('visibilitychange', onVis);
return () => document.removeEventListener('visibilitychange', onVis);
}, []);
useEffect(() => {
if (!canPoll) {
setLoading(false);
return;
}
setLoading(true);
const activeKey = String(eventKey);
fetchOnce(activeKey);
if (timer.current) window.clearInterval(timer.current);
if (visible) {
timer.current = window.setInterval(() => fetchOnce(activeKey), 10_000);
}
return () => {
if (timer.current) window.clearInterval(timer.current);
};
}, [eventKey, visible, canPoll]);
return { ...data, loading };
}