Files
fotospiel-app/resources/js/guest-v2/hooks/usePollGalleryDelta.ts
2026-02-02 13:01:20 +01:00

83 lines
2.2 KiB
TypeScript

import React from 'react';
import { fetchGallery } from '../services/photosApi';
export type GalleryDelta = {
photos: Record<string, unknown>[];
latestPhotoAt: string | null;
nextCursor: string | null;
};
const emptyDelta: GalleryDelta = {
photos: [],
latestPhotoAt: null,
nextCursor: null,
};
export function usePollGalleryDelta(
eventToken: string | null,
options: { intervalMs?: number; locale?: string } = {}
) {
const intervalMs = options.intervalMs ?? 30000;
const [data, setData] = React.useState<GalleryDelta>(emptyDelta);
const [loading, setLoading] = React.useState(Boolean(eventToken));
const [error, setError] = React.useState<string | null>(null);
const latestRef = React.useRef<string | null>(null);
React.useEffect(() => {
if (!eventToken) {
setData(emptyDelta);
setLoading(false);
setError(null);
latestRef.current = null;
return;
}
let active = true;
let timer: number | null = null;
const poll = async () => {
if (document.visibilityState === 'hidden') {
timer = window.setTimeout(poll, intervalMs);
return;
}
try {
setLoading(true);
const response = await fetchGallery(eventToken, {
since: latestRef.current ?? undefined,
locale: options.locale,
});
if (!active) return;
const photos = Array.isArray(response.data) ? response.data : [];
const latestPhotoAt = response.latest_photo_at ?? latestRef.current ?? null;
latestRef.current = latestPhotoAt;
setData({
photos,
latestPhotoAt,
nextCursor: response.next_cursor ?? null,
});
setError(null);
} catch (err) {
if (!active) return;
setError(err instanceof Error ? err.message : 'Failed to load gallery updates');
} finally {
if (active) {
setLoading(false);
timer = window.setTimeout(poll, intervalMs);
}
}
};
poll();
return () => {
active = false;
if (timer) {
window.clearTimeout(timer);
}
};
}, [eventToken, intervalMs, options.locale]);
return { data, loading, error } as const;
}