W-02: Reset hasPrefetched ref when app re-locks so subsequent unlocks refresh stale cache data. S-01: Validate localStorage HSL values with regex to prevent CSS injection. S-05: Defer prefetch until settings are loaded for accurate upcoming_days. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
import api from '@/lib/api';
|
|
import { useSettings } from './useSettings';
|
|
|
|
/**
|
|
* Prefetches main page data in the background after the app unlocks.
|
|
* Ensures cache is warm before the user navigates to each tab,
|
|
* eliminating the skeleton flash on first visit.
|
|
*/
|
|
export function usePrefetch(enabled: boolean) {
|
|
const queryClient = useQueryClient();
|
|
const { settings } = useSettings();
|
|
const hasPrefetched = useRef(false);
|
|
const prevEnabled = useRef(false);
|
|
|
|
// Reset on re-lock so subsequent unlocks refresh stale data (W-02)
|
|
useEffect(() => {
|
|
if (prevEnabled.current && !enabled) {
|
|
hasPrefetched.current = false;
|
|
}
|
|
prevEnabled.current = enabled;
|
|
}, [enabled]);
|
|
|
|
useEffect(() => {
|
|
// Wait for settings to load so upcoming_days is accurate (S-05)
|
|
if (!enabled || hasPrefetched.current || !settings) return;
|
|
hasPrefetched.current = true;
|
|
|
|
const now = new Date();
|
|
const clientDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
const days = settings.upcoming_days || 7;
|
|
|
|
// Prefetch all main page queries (no-op if already cached and fresh)
|
|
queryClient.prefetchQuery({
|
|
queryKey: ['dashboard'],
|
|
queryFn: () => api.get(`/dashboard?client_date=${clientDate}`).then(r => r.data),
|
|
staleTime: 60_000,
|
|
});
|
|
|
|
queryClient.prefetchQuery({
|
|
queryKey: ['upcoming', days],
|
|
queryFn: () => api.get(`/upcoming?days=${days}&client_date=${clientDate}`).then(r => r.data),
|
|
staleTime: 60_000,
|
|
});
|
|
|
|
queryClient.prefetchQuery({
|
|
queryKey: ['todos'],
|
|
queryFn: () => api.get('/todos').then(r => r.data),
|
|
});
|
|
|
|
queryClient.prefetchQuery({
|
|
queryKey: ['reminders'],
|
|
queryFn: () => api.get('/reminders').then(r => r.data),
|
|
});
|
|
|
|
queryClient.prefetchQuery({
|
|
queryKey: ['projects'],
|
|
queryFn: () => api.get('/projects').then(r => r.data),
|
|
});
|
|
|
|
queryClient.prefetchQuery({
|
|
queryKey: ['people'],
|
|
queryFn: () => api.get('/people').then(r => r.data),
|
|
});
|
|
|
|
queryClient.prefetchQuery({
|
|
queryKey: ['locations'],
|
|
queryFn: () => api.get('/locations').then(r => r.data),
|
|
});
|
|
}, [enabled, queryClient, settings]);
|
|
}
|