Morning improvements

This commit is contained in:
Andras Schmelczer 2026-03-17 13:29:03 +00:00
parent 3e9fba5303
commit 53fff3efaa
41 changed files with 2438 additions and 637 deletions

View file

@ -1,4 +1,4 @@
import { useState, useCallback } from 'react';
import { useState, useCallback, useRef, useEffect } from 'react';
import pb from '../lib/pocketbase';
import { apiUrl, authHeaders } from '../lib/api';
import { trackEvent } from '../lib/analytics';
@ -12,39 +12,94 @@ export interface SavedSearch {
created: string;
}
const POLL_INTERVAL_MS = 2000;
const MAX_POLL_ATTEMPTS = 15;
export function useSavedSearches(userId: string | null) {
const [searches, setSearches] = useState<SavedSearch[]>([]);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const pollAttemptsRef = useRef(0);
const userIdRef = useRef(userId);
userIdRef.current = userId;
const stopPolling = useCallback(() => {
if (pollTimerRef.current) {
clearInterval(pollTimerRef.current);
pollTimerRef.current = null;
}
pollAttemptsRef.current = 0;
}, []);
// Clean up polling on unmount or userId change
useEffect(() => stopPolling, [userId, stopPolling]);
const fetchRecords = useCallback(async (uid: string): Promise<SavedSearch[]> => {
const records = await pb.collection('saved_searches').getFullList({
sort: '-created',
filter: `user = "${uid}"`,
});
return records.map((r) => ({
id: r.id,
name: (r as Record<string, unknown>).name as string,
params: (r as Record<string, unknown>).params as string,
screenshotUrl: (r as Record<string, unknown>).screenshot
? pb.files.getURL(r, (r as Record<string, unknown>).screenshot as string)
: '',
notes: ((r as Record<string, unknown>).notes as string) || '',
created: r.created,
}));
}, []);
const startPolling = useCallback(() => {
if (pollTimerRef.current) return;
pollAttemptsRef.current = 0;
pollTimerRef.current = setInterval(async () => {
const uid = userIdRef.current;
if (!uid) {
stopPolling();
return;
}
pollAttemptsRef.current++;
if (pollAttemptsRef.current >= MAX_POLL_ATTEMPTS) {
stopPolling();
return;
}
try {
const mapped = await fetchRecords(uid);
setSearches(mapped);
if (!mapped.some((s) => !s.screenshotUrl)) {
stopPolling();
}
} catch {
// Silent — background poll errors don't surface to UI
}
}, POLL_INTERVAL_MS);
}, [stopPolling, fetchRecords]);
const fetchSearches = useCallback(async () => {
if (!userId) return;
setLoading(true);
setError(null);
try {
const records = await pb.collection('saved_searches').getFullList({
sort: '-created',
filter: `user = "${userId}"`,
});
setSearches(
records.map((r) => ({
id: r.id,
name: (r as Record<string, unknown>).name as string,
params: (r as Record<string, unknown>).params as string,
screenshotUrl: (r as Record<string, unknown>).screenshot
? pb.files.getURL(r, (r as Record<string, unknown>).screenshot as string)
: '',
notes: ((r as Record<string, unknown>).notes as string) || '',
created: r.created,
}))
);
const mapped = await fetchRecords(userId);
setSearches(mapped);
// Poll for missing screenshots so they appear without a page refresh
if (mapped.some((s) => !s.screenshotUrl)) {
startPolling();
} else {
stopPolling();
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load searches');
} finally {
setLoading(false);
}
}, [userId]);
}, [userId, fetchRecords, startPolling, stopPolling]);
const saveSearch = useCallback(
async (name: string) => {