courrier multi account restore compose
This commit is contained in:
parent
346b766b7f
commit
9f0ca0e6f5
@ -7,6 +7,9 @@ import {
|
|||||||
cacheEmailList,
|
cacheEmailList,
|
||||||
invalidateFolderCache
|
invalidateFolderCache
|
||||||
} from '@/lib/redis';
|
} from '@/lib/redis';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
// Simple in-memory cache (will be removed in a future update)
|
// Simple in-memory cache (will be removed in a future update)
|
||||||
interface EmailCacheEntry {
|
interface EmailCacheEntry {
|
||||||
@ -38,11 +41,32 @@ export async function GET(request: Request) {
|
|||||||
const accountId = searchParams.get("accountId") || "";
|
const accountId = searchParams.get("accountId") || "";
|
||||||
|
|
||||||
// Extract account ID from folder name if present and none was explicitly provided
|
// Extract account ID from folder name if present and none was explicitly provided
|
||||||
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : "";
|
||||||
|
|
||||||
// Use the most specific account ID available
|
// Use the most specific account ID available
|
||||||
|
// First from the folder, then from the explicit parameter, then default
|
||||||
let effectiveAccountId = folderAccountId || accountId || 'default';
|
let effectiveAccountId = folderAccountId || accountId || 'default';
|
||||||
|
|
||||||
|
// CRITICAL FIX: If effectiveAccountId is still 'default', try to find the first account for the user
|
||||||
|
if (effectiveAccountId === 'default') {
|
||||||
|
try {
|
||||||
|
const accounts = await prisma.mailCredentials.findMany({
|
||||||
|
where: { userId: session.user.id },
|
||||||
|
orderBy: { createdAt: 'asc' },
|
||||||
|
take: 1,
|
||||||
|
select: { id: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (accounts && accounts.length > 0) {
|
||||||
|
effectiveAccountId = accounts[0].id;
|
||||||
|
console.log(`No specific account provided, using first available account: ${effectiveAccountId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error finding default account:", error);
|
||||||
|
// Continue with 'default' if there's an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize folder name by removing account prefix if present
|
// Normalize folder name by removing account prefix if present
|
||||||
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
|
||||||
|
|||||||
@ -95,15 +95,23 @@ export const useCourrier = () => {
|
|||||||
// The currentFolder should already have the account prefix in format "accountId:folder"
|
// The currentFolder should already have the account prefix in format "accountId:folder"
|
||||||
// We need to extract the base folder name for the API request
|
// We need to extract the base folder name for the API request
|
||||||
let normalizedFolder = currentFolder;
|
let normalizedFolder = currentFolder;
|
||||||
let effectiveAccountId = accountId || 'default';
|
|
||||||
|
|
||||||
if (currentFolder.includes(':')) {
|
// CRITICAL FIX: Handle account ID determination more robustly
|
||||||
|
// If specific account ID is provided, always use it
|
||||||
|
// Otherwise extract from currentFolder if possible
|
||||||
|
let effectiveAccountId: string;
|
||||||
|
|
||||||
|
if (accountId) {
|
||||||
|
// Use explicitly provided accountId
|
||||||
|
effectiveAccountId = accountId;
|
||||||
|
} else if (currentFolder.includes(':')) {
|
||||||
|
// Extract from folder format
|
||||||
const parts = currentFolder.split(':');
|
const parts = currentFolder.split(':');
|
||||||
// If no explicit accountId was provided, use the one from the folder name
|
effectiveAccountId = parts[0];
|
||||||
if (!accountId) {
|
|
||||||
effectiveAccountId = parts[0];
|
|
||||||
}
|
|
||||||
normalizedFolder = parts[1];
|
normalizedFolder = parts[1];
|
||||||
|
} else {
|
||||||
|
// Default case
|
||||||
|
effectiveAccountId = 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Load emails - using normalized folder: ${normalizedFolder}, effectiveAccountId: ${effectiveAccountId}`);
|
console.log(`Load emails - using normalized folder: ${normalizedFolder}, effectiveAccountId: ${effectiveAccountId}`);
|
||||||
@ -125,12 +133,14 @@ export const useCourrier = () => {
|
|||||||
// Try to get cached emails first
|
// Try to get cached emails first
|
||||||
const currentRequestPage = page;
|
const currentRequestPage = page;
|
||||||
|
|
||||||
// FIXED: Use the correct parameter order and normalized values
|
// CRITICAL FIX: Use the correct cache key format
|
||||||
// Function signature: (userId: string, folder: string, page: number, perPage: number, timeoutMs: number = 100, accountId?: string)
|
// We pass the full prefixed folder to ensure proper cache key consistency
|
||||||
console.log(`Getting cached emails for user ${session.user.id}, folder ${currentFolder}, normalizedFolder: ${normalizedFolder}, page ${currentRequestPage}, accountId ${effectiveAccountId}`);
|
const folderForCache = `${effectiveAccountId}:${normalizedFolder}`;
|
||||||
|
|
||||||
|
console.log(`Getting cached emails for user ${session.user.id}, folder ${folderForCache}, page ${currentRequestPage}, accountId ${effectiveAccountId}`);
|
||||||
const cachedEmails = await getCachedEmailsWithTimeout(
|
const cachedEmails = await getCachedEmailsWithTimeout(
|
||||||
session.user.id, // userId: string
|
session.user.id, // userId: string
|
||||||
currentFolder, // folder: string - use full prefixed folder for cache key
|
folderForCache, // folder: string - use consistently prefixed folder for cache key
|
||||||
currentRequestPage, // page: number
|
currentRequestPage, // page: number
|
||||||
perPage, // perPage: number
|
perPage, // perPage: number
|
||||||
100, // timeoutMs: number
|
100, // timeoutMs: number
|
||||||
@ -299,8 +309,13 @@ export const useCourrier = () => {
|
|||||||
const changeFolder = useCallback(async (folder: string, accountId?: string) => {
|
const changeFolder = useCallback(async (folder: string, accountId?: string) => {
|
||||||
console.log(`Changing folder to ${folder} for account ${accountId || 'default'}`);
|
console.log(`Changing folder to ${folder} for account ${accountId || 'default'}`);
|
||||||
try {
|
try {
|
||||||
// CRITICAL FIX: Better folder and account ID handling
|
// Reset selected email and selection state immediately to avoid race conditions
|
||||||
// Extract account ID from folder name if present and none was explicitly provided
|
setSelectedEmail(null);
|
||||||
|
setSelectedEmailIds([]);
|
||||||
|
setEmails([]); // Clear existing emails right away
|
||||||
|
setIsLoading(true); // Show loading state immediately
|
||||||
|
|
||||||
|
// CRITICAL FIX: Extract account ID from folder name if present and none was explicitly provided
|
||||||
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
||||||
|
|
||||||
// Use the most specific account ID available
|
// Use the most specific account ID available
|
||||||
@ -314,13 +329,8 @@ export const useCourrier = () => {
|
|||||||
|
|
||||||
console.log(`Folder change: original=${folder}, normalized=${normalizedFolder}, accountId=${effectiveAccountId}, prefixed=${prefixedFolder}`);
|
console.log(`Folder change: original=${folder}, normalized=${normalizedFolder}, accountId=${effectiveAccountId}, prefixed=${prefixedFolder}`);
|
||||||
|
|
||||||
// Reset selected email
|
// CRITICAL FIX: Store the current account ID in state for all subsequent operations
|
||||||
setSelectedEmail(null);
|
// This ensures operations like markAsRead use the correct account context
|
||||||
setSelectedEmailIds([]);
|
|
||||||
|
|
||||||
// Use the consistently prefixed folder name for state
|
|
||||||
// CRITICAL FIX: Always use the properly prefixed folder name in state
|
|
||||||
setCurrentFolder(prefixedFolder);
|
|
||||||
|
|
||||||
// Reset search query when changing folders
|
// Reset search query when changing folders
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
@ -328,19 +338,16 @@ export const useCourrier = () => {
|
|||||||
// Reset to page 1
|
// Reset to page 1
|
||||||
setPage(1);
|
setPage(1);
|
||||||
|
|
||||||
// Clear existing emails before loading new ones to prevent UI flicker
|
// CRITICAL FIX: We set the currentFolder state AFTER we have prepared all parameters
|
||||||
setEmails([]);
|
// This ensures any effects or functions triggered by currentFolder change have the correct context
|
||||||
|
setCurrentFolder(prefixedFolder);
|
||||||
// Show loading state
|
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
// Use a small delay to ensure state updates have propagated
|
// Use a small delay to ensure state updates have propagated
|
||||||
// This helps prevent race conditions when multiple folders are clicked quickly
|
// This helps prevent race conditions when multiple folders are clicked quickly
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
// Call loadEmails with correct boolean parameter type and account ID
|
// CRITICAL FIX: Wait for the loadEmails operation to complete before considering the folder change done
|
||||||
// CRITICAL FIX: Pass the properly formatted folder name to the cache lookup functions
|
// This prevents multiple concurrent folder changes from interfering with each other
|
||||||
console.log(`Loading emails for prefixed folder: ${prefixedFolder} with accountId: ${effectiveAccountId}`);
|
|
||||||
await loadEmails(false, effectiveAccountId);
|
await loadEmails(false, effectiveAccountId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error changing to folder ${folder}:`, error);
|
console.error(`Error changing to folder ${folder}:`, error);
|
||||||
@ -408,24 +415,47 @@ export const useCourrier = () => {
|
|||||||
}, [currentFolder]);
|
}, [currentFolder]);
|
||||||
|
|
||||||
// Mark an email as read/unread
|
// Mark an email as read/unread
|
||||||
const markEmailAsRead = useCallback(async (emailId: string, isRead: boolean) => {
|
const markEmailAsRead = useCallback(async (emailId: string, isRead: boolean, providedAccountId?: string) => {
|
||||||
try {
|
try {
|
||||||
// Find the email to get its accountId
|
// CRITICAL FIX: If an account ID is provided, use it directly
|
||||||
const emailToMark = emails.find(e => e.id === emailId);
|
// Otherwise, find the email to get its accountId
|
||||||
if (!emailToMark) {
|
let emailAccountId = providedAccountId;
|
||||||
throw new Error('Email not found');
|
let emailFolder = '';
|
||||||
|
|
||||||
|
if (!emailAccountId) {
|
||||||
|
// Find the email in the current list
|
||||||
|
const emailToMark = emails.find(e => e.id === emailId);
|
||||||
|
if (!emailToMark) {
|
||||||
|
throw new Error('Email not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the accountId from the email
|
||||||
|
emailAccountId = emailToMark.accountId || 'default';
|
||||||
|
emailFolder = emailToMark.folder;
|
||||||
|
} else {
|
||||||
|
// If providedAccountId exists but we don't have folder info,
|
||||||
|
// try to find the email in the list to get its folder
|
||||||
|
const emailToMark = emails.find(e => e.id === emailId && e.accountId === providedAccountId);
|
||||||
|
if (emailToMark) {
|
||||||
|
emailFolder = emailToMark.folder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the accountId from the email
|
|
||||||
const emailAccountId = emailToMark.accountId || 'default';
|
|
||||||
|
|
||||||
// Normalize folder name by removing account prefix if present
|
// Normalize folder name by removing account prefix if present
|
||||||
const normalizedFolder = emailToMark.folder.includes(':')
|
let normalizedFolder = emailFolder;
|
||||||
? emailToMark.folder.split(':')[1]
|
if (emailFolder && emailFolder.includes(':')) {
|
||||||
: emailToMark.folder;
|
normalizedFolder = emailFolder.split(':')[1];
|
||||||
|
} else if (!emailFolder) {
|
||||||
|
// If folder isn't available from the email object, try to extract it from currentFolder
|
||||||
|
if (currentFolder.includes(':')) {
|
||||||
|
normalizedFolder = currentFolder.split(':')[1];
|
||||||
|
} else {
|
||||||
|
normalizedFolder = currentFolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Marking email ${emailId} as ${isRead ? 'read' : 'unread'} in folder ${normalizedFolder}, account: ${emailAccountId}`);
|
console.log(`Marking email ${emailId} as ${isRead ? 'read' : 'unread'} in folder ${normalizedFolder}, account: ${emailAccountId}`);
|
||||||
|
|
||||||
const response = await fetch(`/api/courrier/${emailId}/mark-read`, {
|
const response = await fetch(`/api/courrier/${emailId}/mark-read`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -437,27 +467,29 @@ export const useCourrier = () => {
|
|||||||
accountId: emailAccountId
|
accountId: emailAccountId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to mark email as read');
|
throw new Error('Failed to mark email as read');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the email in the list
|
// Update the email in the list - only update the specific email with matching ID AND account ID
|
||||||
setEmails(emails.map(email =>
|
setEmails(emails.map(email =>
|
||||||
email.id === emailId ? { ...email, flags: { ...email.flags, seen: isRead } } : email
|
(email.id === emailId && (!providedAccountId || email.accountId === providedAccountId))
|
||||||
|
? { ...email, flags: { ...email.flags, seen: isRead } }
|
||||||
|
: email
|
||||||
));
|
));
|
||||||
|
|
||||||
// If the selected email is the one being marked, update it too
|
// If the selected email is the one being marked, update it too
|
||||||
if (selectedEmail && selectedEmail.id === emailId) {
|
if (selectedEmail && selectedEmail.id === emailId && (!providedAccountId || selectedEmail.accountId === providedAccountId)) {
|
||||||
setSelectedEmail({ ...selectedEmail, flags: { ...selectedEmail.flags, seen: isRead } });
|
setSelectedEmail({ ...selectedEmail, flags: { ...selectedEmail.flags, seen: isRead } });
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error marking email as read:', error);
|
console.error('Error marking email as read:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, [emails, selectedEmail]);
|
}, [emails, selectedEmail, currentFolder]);
|
||||||
|
|
||||||
// Select an email to view
|
// Select an email to view
|
||||||
const handleEmailSelect = useCallback(async (emailId: string, accountId: string, folderOverride: string) => {
|
const handleEmailSelect = useCallback(async (emailId: string, accountId: string, folderOverride: string) => {
|
||||||
@ -473,8 +505,11 @@ export const useCourrier = () => {
|
|||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
// Normalize account ID if not provided
|
// CRITICAL FIX: Always use the provided accountId, never use default
|
||||||
const effectiveAccountId = accountId || 'default';
|
// This ensures we consistently use the correct account throughout the email selection process
|
||||||
|
if (!accountId) {
|
||||||
|
throw new Error('Account ID is required for email selection');
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize folder name handling - ensure consistent format
|
// Normalize folder name handling - ensure consistent format
|
||||||
let normalizedFolder: string;
|
let normalizedFolder: string;
|
||||||
@ -483,76 +518,59 @@ export const useCourrier = () => {
|
|||||||
if (folderOverride.includes(':')) {
|
if (folderOverride.includes(':')) {
|
||||||
// Extract parts if folder already has a prefix
|
// Extract parts if folder already has a prefix
|
||||||
const parts = folderOverride.split(':');
|
const parts = folderOverride.split(':');
|
||||||
const folderAccountId = parts[0];
|
|
||||||
normalizedFolder = parts[1];
|
normalizedFolder = parts[1];
|
||||||
|
|
||||||
// CRITICAL FIX: Always use the provided accountId instead of the one in the folder
|
// CRITICAL FIX: Always use the explicitly provided accountId
|
||||||
// This ensures we're looking in the right account when an email is clicked
|
// This ensures we're looking in the right account when an email is clicked
|
||||||
prefixedFolder = `${effectiveAccountId}:${normalizedFolder}`;
|
prefixedFolder = `${accountId}:${normalizedFolder}`;
|
||||||
|
|
||||||
if (folderAccountId !== effectiveAccountId) {
|
|
||||||
console.log(`WARNING: Folder account prefix mismatch. Folder has ${folderAccountId}, but using ${effectiveAccountId}`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// No prefix, add one
|
// No prefix, add one using the provided account ID
|
||||||
normalizedFolder = folderOverride;
|
normalizedFolder = folderOverride;
|
||||||
prefixedFolder = `${effectiveAccountId}:${normalizedFolder}`;
|
prefixedFolder = `${accountId}:${normalizedFolder}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Email selection with normalized values: folder=${normalizedFolder}, prefixed=${prefixedFolder}, accountId=${effectiveAccountId}`);
|
console.log(`Email selection with normalized values: folder=${normalizedFolder}, prefixed=${prefixedFolder}, accountId=${accountId}`);
|
||||||
|
|
||||||
// More flexible email finding with detailed logging
|
// CRITICAL FIX: First search for email in current list using the EXACT account provided
|
||||||
console.log(`Looking for email with ID=${emailId}, account=${effectiveAccountId}, normalized folder=${normalizedFolder}, prefixed=${prefixedFolder}`);
|
// This ensures we don't mix emails from different accounts
|
||||||
|
console.log(`Looking for email with ID=${emailId}, account=${accountId}, normalized folder=${normalizedFolder}, prefixed=${prefixedFolder}`);
|
||||||
|
|
||||||
// First, try to find by exact match with account and folder
|
// First, try to find by exact match with the provided account and folder
|
||||||
let email = emails.find(e =>
|
let email = emails.find(e =>
|
||||||
e.id === emailId &&
|
e.id === emailId &&
|
||||||
e.accountId === effectiveAccountId &&
|
e.accountId === accountId &&
|
||||||
(
|
(
|
||||||
e.folder === prefixedFolder ||
|
e.folder === prefixedFolder ||
|
||||||
e.folder === normalizedFolder ||
|
e.folder === normalizedFolder ||
|
||||||
e.folder === folderOverride ||
|
|
||||||
// Also check for case where email folder has its own prefix but with the same normalized folder
|
|
||||||
(e.folder?.includes(':') && e.folder.split(':')[1] === normalizedFolder)
|
(e.folder?.includes(':') && e.folder.split(':')[1] === normalizedFolder)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// If not found, try to find just by ID as fallback
|
// CRITICAL FIX: If not found, we do NOT try finding by ID only
|
||||||
|
// This prevents mixing emails across accounts
|
||||||
if (!email) {
|
if (!email) {
|
||||||
console.log(`No exact match found. Looking for email just by ID=${emailId}`);
|
console.log(`Email ${emailId} not found in current list for account ${accountId} (searched ${emails.length} emails). Fetching from API.`);
|
||||||
email = emails.find(e => e.id === emailId);
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
console.log(`Found email by ID only. Account=${email.accountId}, folder=${email.folder}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!email) {
|
|
||||||
console.log(`Email ${emailId} not found in current list (searched ${emails.length} emails). Fetching from API.`);
|
|
||||||
try {
|
try {
|
||||||
// CRITICAL FIX: Pass both normalized folder and account ID to fetch
|
// Use the provided account ID and normalized folder for the API request
|
||||||
// Using normalized folder (without prefix) for API compatibility
|
console.log(`Fetching email ${emailId} directly from API with normalizedFolder=${normalizedFolder}, accountId=${accountId}`);
|
||||||
console.log(`Fetching email ${emailId} directly from API with normalizedFolder=${normalizedFolder}, accountId=${effectiveAccountId}`);
|
const fullEmail = await fetchEmailContent(emailId, accountId, normalizedFolder);
|
||||||
const fullEmail = await fetchEmailContent(emailId, effectiveAccountId, normalizedFolder);
|
|
||||||
|
|
||||||
// Ensure the returned email has the proper accountId and prefixed folder name
|
// CRITICAL FIX: Always set the accountId correctly
|
||||||
if (fullEmail) {
|
if (fullEmail) {
|
||||||
if (!fullEmail.accountId) {
|
fullEmail.accountId = accountId;
|
||||||
fullEmail.accountId = effectiveAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure folder has the proper prefix for consistent lookup
|
// Make sure folder has the proper prefix for consistent lookup
|
||||||
if (fullEmail.folder && !fullEmail.folder.includes(':')) {
|
if (fullEmail.folder && !fullEmail.folder.includes(':')) {
|
||||||
fullEmail.folder = `${fullEmail.accountId}:${fullEmail.folder}`;
|
fullEmail.folder = `${accountId}:${fullEmail.folder}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Successfully fetched email from API:`, {
|
||||||
|
id: fullEmail.id,
|
||||||
|
account: fullEmail.accountId,
|
||||||
|
folder: fullEmail.folder
|
||||||
|
});
|
||||||
|
setSelectedEmail(fullEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Successfully fetched email from API:`, {
|
|
||||||
id: fullEmail.id,
|
|
||||||
account: fullEmail.accountId,
|
|
||||||
folder: fullEmail.folder
|
|
||||||
});
|
|
||||||
setSelectedEmail(fullEmail);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Type the error properly
|
// Type the error properly
|
||||||
const fetchError = error instanceof Error ? error : new Error(String(error));
|
const fetchError = error instanceof Error ? error : new Error(String(error));
|
||||||
@ -566,43 +584,35 @@ export const useCourrier = () => {
|
|||||||
if (!email.contentFetched) {
|
if (!email.contentFetched) {
|
||||||
console.log(`Email found but content not fetched. Getting full content for ${emailId}`);
|
console.log(`Email found but content not fetched. Getting full content for ${emailId}`);
|
||||||
try {
|
try {
|
||||||
// CRITICAL FIX: Extract normalized folder from email's folder if it has a prefix
|
// CRITICAL FIX: Use the provided accountId for fetching content, not the one from the email
|
||||||
const emailFolder = email.folder || normalizedFolder;
|
// This ensures consistent account context
|
||||||
let emailNormalizedFolder = emailFolder;
|
|
||||||
|
|
||||||
if (emailFolder.includes(':')) {
|
console.log(`Fetching content for email ${emailId} with accountId=${accountId}, folder=${normalizedFolder}`);
|
||||||
emailNormalizedFolder = emailFolder.split(':')[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always use the email's own accountId if available
|
// Use the provided accountId and normalized folder for fetching
|
||||||
const emailAccountId = email.accountId || effectiveAccountId;
|
|
||||||
|
|
||||||
console.log(`Fetching content for email ${emailId} with accountId=${emailAccountId}, folder=${emailNormalizedFolder}`);
|
|
||||||
|
|
||||||
// Use the email's own accountId and normalized folder for fetching
|
|
||||||
const fullEmail = await fetchEmailContent(
|
const fullEmail = await fetchEmailContent(
|
||||||
emailId,
|
emailId,
|
||||||
emailAccountId,
|
accountId,
|
||||||
emailNormalizedFolder
|
normalizedFolder
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure the returned email has consistent format
|
// CRITICAL FIX: Ensure the returned email has the correct account ID
|
||||||
if (fullEmail && !fullEmail.accountId) {
|
if (fullEmail) {
|
||||||
fullEmail.accountId = emailAccountId;
|
fullEmail.accountId = accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge the full content with the email
|
// Merge the full content with the email
|
||||||
const updatedEmail = {
|
const updatedEmail = {
|
||||||
...email,
|
...email,
|
||||||
accountId: emailAccountId, // Ensure account ID is preserved
|
accountId, // CRITICAL FIX: Use the provided accountId
|
||||||
folder: email.folder, // Preserve original folder name with prefix
|
folder: prefixedFolder, // CRITICAL FIX: Use the consistently prefixed folder
|
||||||
content: fullEmail.content,
|
content: fullEmail.content,
|
||||||
attachments: fullEmail.attachments,
|
attachments: fullEmail.attachments,
|
||||||
contentFetched: true
|
contentFetched: true
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the email in the list
|
// Update the email in the list
|
||||||
setEmails(emails.map(e => e.id === emailId ? updatedEmail : e));
|
setEmails(emails.map(e => e.id === emailId && e.accountId === accountId ? updatedEmail : e));
|
||||||
setSelectedEmail(updatedEmail);
|
setSelectedEmail(updatedEmail);
|
||||||
console.log(`Successfully updated email with content`);
|
console.log(`Successfully updated email with content`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -613,13 +623,21 @@ export const useCourrier = () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`Email found with content already fetched, selecting directly`);
|
console.log(`Email found with content already fetched, selecting directly`);
|
||||||
|
|
||||||
|
// CRITICAL FIX: Ensure the email has the correct account ID before selecting
|
||||||
|
email = {
|
||||||
|
...email,
|
||||||
|
accountId, // Always use the provided accountId
|
||||||
|
folder: prefixedFolder // Always use the consistently prefixed folder
|
||||||
|
};
|
||||||
|
|
||||||
setSelectedEmail(email);
|
setSelectedEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the email as read if it's not already
|
// Mark the email as read if it's not already
|
||||||
if (!email.flags.seen) {
|
if (!email.flags.seen) {
|
||||||
console.log(`Marking email ${emailId} as read`);
|
console.log(`Marking email ${emailId} as read for account ${accountId}`);
|
||||||
markEmailAsRead(emailId, true).catch(err => {
|
markEmailAsRead(emailId, true, accountId).catch(err => {
|
||||||
console.error(`Failed to mark email as read: ${err.message}`);
|
console.error(`Failed to mark email as read: ${err.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -637,11 +655,29 @@ export const useCourrier = () => {
|
|||||||
|
|
||||||
// Toggle starred status for an email
|
// Toggle starred status for an email
|
||||||
const toggleStarred = useCallback(async (emailId: string) => {
|
const toggleStarred = useCallback(async (emailId: string) => {
|
||||||
|
// Find the email in the emails array
|
||||||
const email = emails.find(e => e.id === emailId);
|
const email = emails.find(e => e.id === emailId);
|
||||||
if (!email) return;
|
if (!email) return;
|
||||||
|
|
||||||
const newStarredStatus = !email.flags.flagged;
|
const newStarredStatus = !email.flags.flagged;
|
||||||
|
|
||||||
|
// CRITICAL FIX: Extract the account ID from the email object
|
||||||
|
const emailAccountId = email.accountId;
|
||||||
|
if (!emailAccountId) {
|
||||||
|
console.error('Cannot toggle star without account ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract normalized folder from folder with potential prefix
|
||||||
|
let normalizedFolder: string;
|
||||||
|
if (email.folder && email.folder.includes(':')) {
|
||||||
|
normalizedFolder = email.folder.split(':')[1];
|
||||||
|
} else if (currentFolder.includes(':')) {
|
||||||
|
normalizedFolder = currentFolder.split(':')[1];
|
||||||
|
} else {
|
||||||
|
normalizedFolder = email.folder || currentFolder;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/courrier/${emailId}/star`, {
|
const response = await fetch(`/api/courrier/${emailId}/star`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -650,7 +686,8 @@ export const useCourrier = () => {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
starred: newStarredStatus,
|
starred: newStarredStatus,
|
||||||
folder: currentFolder
|
folder: normalizedFolder,
|
||||||
|
accountId: emailAccountId // CRITICAL FIX: Always include account ID in requests
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -658,13 +695,15 @@ export const useCourrier = () => {
|
|||||||
throw new Error('Failed to toggle star status');
|
throw new Error('Failed to toggle star status');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the email in the list
|
// Update the email in the list - match both ID and account ID
|
||||||
setEmails(emails.map(email =>
|
setEmails(emails.map(email =>
|
||||||
email.id === emailId ? { ...email, flags: { ...email.flags, flagged: newStarredStatus } } : email
|
(email.id === emailId && email.accountId === emailAccountId)
|
||||||
|
? { ...email, flags: { ...email.flags, flagged: newStarredStatus } }
|
||||||
|
: email
|
||||||
));
|
));
|
||||||
|
|
||||||
// If the selected email is the one being starred, update it too
|
// If the selected email is the one being starred, update it too
|
||||||
if (selectedEmail && selectedEmail.id === emailId) {
|
if (selectedEmail && selectedEmail.id === emailId && selectedEmail.accountId === emailAccountId) {
|
||||||
setSelectedEmail({ ...selectedEmail, flags: { ...selectedEmail.flags, flagged: newStarredStatus } });
|
setSelectedEmail({ ...selectedEmail, flags: { ...selectedEmail.flags, flagged: newStarredStatus } });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -736,14 +775,41 @@ export const useCourrier = () => {
|
|||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// CRITICAL FIX: Extract normalized folder and account ID from currentFolder
|
||||||
|
let normalizedFolder = currentFolder;
|
||||||
|
let accountId = 'default';
|
||||||
|
|
||||||
|
if (currentFolder.includes(':')) {
|
||||||
|
const parts = currentFolder.split(':');
|
||||||
|
accountId = parts[0];
|
||||||
|
normalizedFolder = parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter email IDs based on the current account context
|
||||||
|
// Only delete emails that belong to the current account
|
||||||
|
const emailsInCurrentAccount = emails.filter(email =>
|
||||||
|
emailIds.includes(email.id) &&
|
||||||
|
(!email.accountId || email.accountId === accountId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredEmailIds = emailsInCurrentAccount.map(email => email.id);
|
||||||
|
|
||||||
|
if (filteredEmailIds.length === 0) {
|
||||||
|
console.log('No emails to delete in the current account context');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Deleting ${filteredEmailIds.length} emails from account ${accountId} in folder ${normalizedFolder}`);
|
||||||
|
|
||||||
const response = await fetch('/api/courrier/delete', {
|
const response = await fetch('/api/courrier/delete', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
emailIds,
|
emailIds: filteredEmailIds,
|
||||||
folder: currentFolder
|
folder: normalizedFolder,
|
||||||
|
accountId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -752,19 +818,23 @@ export const useCourrier = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the deleted emails from the list
|
// Remove the deleted emails from the list
|
||||||
setEmails(emails.filter(email => !emailIds.includes(email.id)));
|
setEmails(emails.filter(email =>
|
||||||
|
!filteredEmailIds.includes(email.id) ||
|
||||||
|
(email.accountId && email.accountId !== accountId)
|
||||||
|
));
|
||||||
|
|
||||||
// Clear selection if the selected email was deleted
|
// Clear selection if the selected email was deleted
|
||||||
if (selectedEmail && emailIds.includes(selectedEmail.id)) {
|
if (selectedEmail && filteredEmailIds.includes(selectedEmail.id) &&
|
||||||
|
(!selectedEmail.accountId || selectedEmail.accountId === accountId)) {
|
||||||
setSelectedEmail(null);
|
setSelectedEmail(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear selected IDs
|
// Clear selected IDs
|
||||||
setSelectedEmailIds([]);
|
setSelectedEmailIds(prevIds => prevIds.filter(id => !filteredEmailIds.includes(id)));
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
description: `${emailIds.length} email(s) deleted`
|
description: `${filteredEmailIds.length} email(s) deleted`
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting emails:', error);
|
console.error('Error deleting emails:', error);
|
||||||
@ -803,8 +873,17 @@ export const useCourrier = () => {
|
|||||||
const searchEmails = useCallback((query: string) => {
|
const searchEmails = useCallback((query: string) => {
|
||||||
setSearchQuery(query);
|
setSearchQuery(query);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
loadEmails();
|
|
||||||
}, [loadEmails]);
|
// CRITICAL FIX: Extract account ID from currentFolder when searching
|
||||||
|
let accountId = 'default';
|
||||||
|
if (currentFolder.includes(':')) {
|
||||||
|
const parts = currentFolder.split(':');
|
||||||
|
accountId = parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call loadEmails with the correct account context
|
||||||
|
loadEmails(false, accountId);
|
||||||
|
}, [loadEmails, currentFolder]);
|
||||||
|
|
||||||
// Format an email for reply or forward
|
// Format an email for reply or forward
|
||||||
const formatEmailForAction = useCallback((email: Email) => {
|
const formatEmailForAction = useCallback((email: Email) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user