83 lines
2.2 KiB
TypeScript
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;
|
|
}
|