diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index f7c7b628..0d086d1b 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -151,9 +151,36 @@ export default function CourrierPage() { setLoading(true); // First check if Redis is ready before making API calls - const redisStatus = await fetch('/api/redis/status') - .then(res => res.json()) - .catch(() => ({ ready: false })); + // Use a cache mechanism to reduce frequency of Redis status checks + const redisCheckCacheKey = 'neah_redis_status_check'; + const cachedRedisCheck = localStorage.getItem(redisCheckCacheKey); + let redisStatus = { ready: false }; + + if (cachedRedisCheck) { + try { + const { status, timestamp } = JSON.parse(cachedRedisCheck); + // Only use cache if it's less than 2 minutes old + if (Date.now() - timestamp < 2 * 60 * 1000) { + redisStatus = status; + console.log('Using cached Redis status check'); + } + } catch (e) { + // Invalid JSON in cache, ignore and fetch fresh status + } + } + + // Only check Redis status if we don't have a recent cached result + if (!redisStatus.ready) { + redisStatus = await fetch('/api/redis/status') + .then(res => res.json()) + .catch(() => ({ ready: false })); + + // Cache the result + localStorage.setItem(redisCheckCacheKey, JSON.stringify({ + status: redisStatus, + timestamp: Date.now() + })); + } if (!isMounted) return; @@ -373,7 +400,8 @@ export default function CourrierPage() { return ( <> - + {/* Only render RedisCacheStatus in development mode to avoid unnecessary status checks */} + {process.env.NODE_ENV === 'development' && } {/* Main layout */}
diff --git a/components/email/EmailList.tsx b/components/email/EmailList.tsx index 4ecf373f..26df0b10 100644 --- a/components/email/EmailList.tsx +++ b/components/email/EmailList.tsx @@ -44,10 +44,20 @@ export default function EmailList({ const [scrollPosition, setScrollPosition] = useState(0); const [searchQuery, setSearchQuery] = useState(''); const [isLoadingMore, setIsLoadingMore] = useState(false); + const [lastLoadTime, setLastLoadTime] = useState(0); const scrollRef = useRef(null); const scrollTimeoutRef = useRef(null); + const loadMoreTimeoutRef = useRef(null); const prevEmailsLengthRef = useRef(emails.length); + // Clear any pending timeouts on unmount + useEffect(() => { + return () => { + if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current); + if (loadMoreTimeoutRef.current) clearTimeout(loadMoreTimeoutRef.current); + }; + }, []); + // Debounced scroll handler for better performance const handleScroll = useCallback((event: React.UIEvent) => { const target = event.target as HTMLDivElement; @@ -61,11 +71,18 @@ export default function EmailList({ clearTimeout(scrollTimeoutRef.current); } - // If near bottom (within 200px) and more emails are available, load more + // If near bottom (within 300px) and more emails are available, load more // Added additional checks to prevent loading loop - const isNearBottom = scrollHeight - scrollTop - clientHeight < 300; // Increased detection area - if (isNearBottom && hasMoreEmails && !isLoading && !isLoadingMore) { + const isNearBottom = scrollHeight - scrollTop - clientHeight < 300; + + // Don't trigger load if we're already loading or if the last load was too recent (throttle) + const now = Date.now(); + const timeSinceLastLoad = now - lastLoadTime; + const tooSoonToLoadAgain = timeSinceLastLoad < 2000; // 2 seconds throttle + + if (isNearBottom && hasMoreEmails && !isLoading && !isLoadingMore && !tooSoonToLoadAgain) { setIsLoadingMore(true); + setLastLoadTime(now); // Use timeout to debounce load requests scrollTimeoutRef.current = setTimeout(() => { @@ -76,13 +93,15 @@ export default function EmailList({ onLoadMore(); // Reset loading state after a delay - setTimeout(() => { + if (loadMoreTimeoutRef.current) clearTimeout(loadMoreTimeoutRef.current); + loadMoreTimeoutRef.current = setTimeout(() => { + loadMoreTimeoutRef.current = null; console.log('Resetting loading more state after timeout'); setIsLoadingMore(false); - }, 3000); // Increased from 1500ms to 3000ms to allow more time for loading - }, 300); // Increased from 200ms to 300ms for better debouncing + }, 2000); // Reduced from 3000ms to 2000ms to avoid long loading states + }, 200); // Reduced from 300ms to 200ms for better responsiveness } - }, [hasMoreEmails, isLoading, isLoadingMore, onLoadMore]); + }, [hasMoreEmails, isLoading, isLoadingMore, onLoadMore, lastLoadTime]); // Restore scroll position when emails are loaded useEffect(() => { @@ -93,8 +112,13 @@ export default function EmailList({ // 4. We're not in the middle of a loading operation if (emails.length > prevEmailsLengthRef.current && scrollRef.current && - scrollPosition > 0 && - !isLoading) { + scrollPosition > 0) { + // If emails have been loaded, force reset the loading state + if (isLoadingMore) { + console.log('Emails loaded, resetting loading state'); + setIsLoadingMore(false); + } + // Use requestAnimationFrame to ensure the DOM has updated requestAnimationFrame(() => { if (scrollRef.current) { @@ -106,7 +130,7 @@ export default function EmailList({ // Always update the reference for next comparison prevEmailsLengthRef.current = emails.length; - }, [emails.length, scrollPosition, isLoading]); + }, [emails.length, scrollPosition, isLoadingMore]); // Add safety mechanism to reset loading state if we get stuck useEffect(() => { @@ -116,13 +140,13 @@ export default function EmailList({ setIsLoadingMore(false); } - // Add a timeout-based safety mechanism + // Add a timeout-based safety mechanism - reduced from 5000ms to 3000ms const safetyTimeout = setTimeout(() => { if (isLoadingMore) { console.log('Safety timeout: Resetting stuck loading state'); setIsLoadingMore(false); } - }, 5000); + }, 3000); return () => clearTimeout(safetyTimeout); }, [emails.length, isLoadingMore]); @@ -279,7 +303,7 @@ export default function EmailList({ {/* Loading indicator */} {(isLoading || isLoadingMore) && ( -
+
Loading more emails...
@@ -290,6 +314,7 @@ export default function EmailList({