diff --git a/app/api/courrier/session/route.ts b/app/api/courrier/session/route.ts
index f58d3d30..7ea20500 100644
--- a/app/api/courrier/session/route.ts
+++ b/app/api/courrier/session/route.ts
@@ -3,7 +3,7 @@ import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { getUserEmailCredentials } from '@/lib/services/email-service';
import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
-import { getCachedEmailCredentials } from '@/lib/redis';
+import { getCachedEmailCredentials, getRedisStatus, warmupRedisCache } from '@/lib/redis';
/**
* This endpoint is called when the app initializes to check if the user has email credentials
@@ -11,6 +11,12 @@ import { getCachedEmailCredentials } from '@/lib/redis';
*/
export async function GET() {
try {
+ // Warm up Redis connection
+ await warmupRedisCache();
+
+ // Get Redis status to include in response
+ const redisStatus = await getRedisStatus();
+
// Get server session to verify authentication
const session = await getServerSession(authOptions);
@@ -18,6 +24,7 @@ export async function GET() {
if (!session?.user?.id) {
return NextResponse.json({
authenticated: false,
+ redisStatus,
message: "Not authenticated"
});
}
@@ -26,10 +33,12 @@ export async function GET() {
// First, check Redis cache for credentials
let credentials = await getCachedEmailCredentials(userId);
+ let credentialsSource = 'cache';
// If not in cache, check database
if (!credentials) {
credentials = await getUserEmailCredentials(userId);
+ credentialsSource = 'database';
}
// If no credentials found
@@ -37,6 +46,7 @@ export async function GET() {
return NextResponse.json({
authenticated: true,
hasEmailCredentials: false,
+ redisStatus,
message: "No email credentials found"
});
}
@@ -52,7 +62,9 @@ export async function GET() {
authenticated: true,
hasEmailCredentials: true,
email: credentials.email,
- prefetchStarted: true
+ redisStatus,
+ prefetchStarted: true,
+ credentialsSource
});
} catch (error) {
console.error("Error checking session:", error);
diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx
index 384e17e3..23dee7f3 100644
--- a/app/courrier/page.tsx
+++ b/app/courrier/page.tsx
@@ -41,6 +41,9 @@ import { useCourrier, EmailData } from '@/hooks/use-courrier';
// Import the prefetching function
import { prefetchFolderEmails } from '@/lib/services/prefetch-service';
+// Import the RedisCacheStatus component
+import { RedisCacheStatus } from '@/components/debug/RedisCacheStatus';
+
// Simplified version for this component
function SimplifiedLoadingFix() {
// In production, don't render anything
@@ -70,7 +73,7 @@ export default function CourrierPage() {
// Get all the email functionality from the hook
const {
- emails,
+ emails = [],
selectedEmail,
selectedEmailIds,
currentFolder,
@@ -130,21 +133,34 @@ export default function CourrierPage() {
// Calculate unread count (this would be replaced with actual data in production)
useEffect(() => {
// Example: counting unread emails in the inbox
- const unreadInInbox = emails.filter(email => !email.read && currentFolder === 'INBOX').length;
+ const unreadInInbox = (emails || []).filter(email => !email.read && currentFolder === 'INBOX').length;
setUnreadCount(unreadInInbox);
}, [emails, currentFolder]);
// Initialize session and start prefetching
useEffect(() => {
+ // Flag to prevent multiple initialization attempts
+ let isMounted = true;
+
const initSession = async () => {
try {
+ // First check if Redis is ready before making API calls
+ const redisStatus = await fetch('/api/redis/status').then(res => res.json()).catch(() => null);
+
// Call the session API to check email credentials and start prefetching
const response = await fetch('/api/courrier/session');
const data = await response.json();
+ if (!isMounted) return;
+
if (data.authenticated && data.hasEmailCredentials) {
console.log('Session initialized, prefetching started');
setPrefetchStarted(true);
+
+ // Preload first page of emails for faster initial rendering
+ if (session?.user?.id) {
+ loadEmails();
+ }
} else if (data.authenticated && !data.hasEmailCredentials) {
// User is authenticated but doesn't have email credentials
setShowLoginNeeded(true);
@@ -155,7 +171,11 @@ export default function CourrierPage() {
};
initSession();
- }, []);
+
+ return () => {
+ isMounted = false;
+ };
+ }, [session?.user?.id, loadEmails]);
// Helper to get folder icons
const getFolderIcon = (folder: string) => {
@@ -290,6 +310,7 @@ export default function CourrierPage() {
return (
<>
+
{/* Main layout */}
diff --git a/components/debug/RedisCacheStatus.tsx b/components/debug/RedisCacheStatus.tsx
new file mode 100644
index 00000000..38b7358d
--- /dev/null
+++ b/components/debug/RedisCacheStatus.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+
+/**
+ * Debug component to show Redis connection status
+ * Only visible in development mode
+ */
+export function RedisCacheStatus() {
+ const [status, setStatus] = useState<'connected' | 'error' | 'loading'>('loading');
+ const [lastCheck, setLastCheck] = useState('');
+
+ useEffect(() => {
+ async function checkRedis() {
+ try {
+ setStatus('loading');
+ const response = await fetch('/api/redis/status');
+ const data = await response.json();
+ setStatus(data.status);
+ setLastCheck(new Date().toLocaleTimeString());
+ } catch (e) {
+ setStatus('error');
+ setLastCheck(new Date().toLocaleTimeString());
+ }
+ }
+
+ checkRedis();
+ const interval = setInterval(checkRedis, 30000); // Check every 30 seconds
+ return () => clearInterval(interval);
+ }, []);
+
+ // Only show in development mode
+ if (process.env.NODE_ENV !== 'development') {
+ return null;
+ }
+
+ return (
+
+
+
+
Redis: {status}
+
({lastCheck})
+
+
+ );
+}
\ No newline at end of file
diff --git a/hooks/use-courrier.ts b/hooks/use-courrier.ts
index 7f9cda49..926c7d95 100644
--- a/hooks/use-courrier.ts
+++ b/hooks/use-courrier.ts
@@ -94,7 +94,16 @@ export const useCourrier = () => {
// First try Redis cache with low timeout
const cachedEmails = await getCachedEmailsWithTimeout(session.user.id, currentFolder, page, perPage, 100);
if (cachedEmails) {
- setEmails(cachedEmails);
+ // Ensure cached data has emails array property
+ if (Array.isArray(cachedEmails.emails)) {
+ setEmails(prevEmails => isLoadMore ? [...prevEmails, ...cachedEmails.emails] : cachedEmails.emails);
+ } else if (Array.isArray(cachedEmails)) {
+ // Direct array response
+ setEmails(prevEmails => isLoadMore ? [...prevEmails, ...cachedEmails] : cachedEmails);
+ } else {
+ console.warn('Invalid cache format:', cachedEmails);
+ }
+
setIsLoading(false);
// Still refresh in background for fresh data
@@ -129,7 +138,8 @@ export const useCourrier = () => {
if (isLoadMore) {
setEmails(prev => [...prev, ...data.emails]);
} else {
- setEmails(data.emails);
+ // Ensure we always set an array even if API returns invalid data
+ setEmails(Array.isArray(data.emails) ? data.emails : []);
}
setTotalEmails(data.totalEmails);
@@ -147,6 +157,8 @@ export const useCourrier = () => {
}
} catch (err) {
console.error('Error loading emails:', err);
+ // Set emails to empty array on error to prevent runtime issues
+ setEmails([]);
setError(err instanceof Error ? err.message : 'Failed to load emails');
toast({
variant: "destructive",
diff --git a/lib/redis.ts b/lib/redis.ts
index 02a5882b..c17c469d 100644
--- a/lib/redis.ts
+++ b/lib/redis.ts
@@ -332,6 +332,45 @@ export async function invalidateEmailContentCache(
await redis.del(key);
}
+/**
+ * Warm up Redis connection to avoid cold starts
+ */
+export async function warmupRedisCache(): Promise {
+ try {
+ // Ping Redis to establish connection early
+ const redis = getRedisClient();
+ await redis.ping();
+ console.log('Redis connection warmed up');
+ return true;
+ } catch (error) {
+ console.error('Error warming up Redis:', error);
+ return false;
+ }
+}
+
+/**
+ * Get Redis connection status
+ */
+export async function getRedisStatus(): Promise<{
+ status: 'connected' | 'error';
+ ping?: string;
+ error?: string;
+}> {
+ try {
+ const redis = getRedisClient();
+ const pong = await redis.ping();
+ return {
+ status: 'connected',
+ ping: pong
+ };
+ } catch (error) {
+ return {
+ status: 'error',
+ error: error instanceof Error ? error.message : String(error)
+ };
+ }
+}
+
/**
* Invalidate all user email caches (email lists and content)
*/
diff --git a/lib/services/prefetch-service.ts b/lib/services/prefetch-service.ts
index c4dbf6dc..8f82636b 100644
--- a/lib/services/prefetch-service.ts
+++ b/lib/services/prefetch-service.ts
@@ -6,7 +6,8 @@ import {
cacheEmailContent,
cacheImapSession,
getCachedEmailList,
- getRedisClient
+ getRedisClient,
+ warmupRedisCache
} from '@/lib/redis';
/**
@@ -31,7 +32,25 @@ export async function getCachedEmailsWithTimeout(
clearTimeout(timeoutId);
if (result) {
console.log(`Using cached data for ${userId}:${folder}:${page}:${perPage}`);
- resolve(result);
+
+ // Validate and normalize the data structure
+ if (typeof result === 'object') {
+ // Make sure we have an emails array
+ if (!result.emails && Array.isArray(result)) {
+ // If result is an array, convert to proper structure
+ resolve({ emails: result });
+ } else if (!result.emails) {
+ // If no emails property, add empty array
+ resolve({ ...result, emails: [] });
+ } else {
+ // Normal case, return as is
+ resolve(result);
+ }
+ } else {
+ // Invalid data, return null
+ console.warn('Invalid cached data format:', result);
+ resolve(null);
+ }
} else {
resolve(null);
}
@@ -167,20 +186,4 @@ export async function prefetchFolderEmails(
} catch (error) {
console.error(`Error prefetching folder ${folder}:`, error);
}
-}
-
-/**
- * Warm up Redis connection to avoid cold starts
- */
-export async function warmupRedisCache(): Promise {
- try {
- // Ping Redis to establish connection early
- const redis = getRedisClient();
- await redis.ping();
- console.log('Redis connection warmed up');
- return true;
- } catch (error) {
- console.error('Error warming up Redis:', error);
- return false;
- }
}
\ No newline at end of file