courrier multi account restore compose
This commit is contained in:
parent
df1570395d
commit
fdd91c4b3d
@ -1,131 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
||||
import {
|
||||
saveUserEmailCredentials,
|
||||
getUserEmailCredentials,
|
||||
testEmailConnection
|
||||
} from '@/lib/services/email-service';
|
||||
import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
|
||||
import {
|
||||
cacheEmailCredentials,
|
||||
invalidateUserEmailCache,
|
||||
getCachedEmailCredentials
|
||||
} from '@/lib/redis';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
// Authenticate user
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get credentials from request
|
||||
const { email, password, host, port } = await request.json();
|
||||
|
||||
// Validate required fields
|
||||
if (!email || !password || !host || !port) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Test connection before saving
|
||||
const connectionSuccess = await testEmailConnection({
|
||||
email,
|
||||
password,
|
||||
host,
|
||||
port: parseInt(port)
|
||||
});
|
||||
|
||||
if (!connectionSuccess) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to connect to email server. Please check your credentials.' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
// Invalidate all cached data for this user as they are changing their credentials
|
||||
await invalidateUserEmailCache(session.user.id);
|
||||
|
||||
// Create credentials object with required fields
|
||||
const credentials = {
|
||||
email,
|
||||
password,
|
||||
host,
|
||||
port: parseInt(port),
|
||||
secure: true // Default to secure connection
|
||||
};
|
||||
|
||||
// Save credentials in the database and Redis
|
||||
// Use email as the accountId since it's unique per user
|
||||
await saveUserEmailCredentials(session.user.id, email, credentials);
|
||||
|
||||
// Start prefetching email data in the background
|
||||
// We don't await this to avoid blocking the response
|
||||
prefetchUserEmailData(session.user.id).catch(err => {
|
||||
console.error('Background prefetch error:', err);
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error in login handler:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'An unexpected error occurred' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
// First try to get from Redis cache
|
||||
let credentials = await getCachedEmailCredentials(session.user.id, 'default');
|
||||
|
||||
// If not in cache, get from database
|
||||
if (!credentials) {
|
||||
credentials = await prisma.mailCredentials.findUnique({
|
||||
where: {
|
||||
userId: session.user.id
|
||||
},
|
||||
select: {
|
||||
email: true,
|
||||
host: true,
|
||||
port: true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Remove password from response
|
||||
const { password, ...safeCredentials } = credentials;
|
||||
credentials = safeCredentials;
|
||||
}
|
||||
|
||||
if (!credentials) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No stored credentials found' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(credentials);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to retrieve credentials' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
||||
export default function MailLoginPage() {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [host, setHost] = useState('mail.infomaniak.com');
|
||||
const [port, setPort] = useState('993');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/courrier/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
host,
|
||||
port,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to connect to email server');
|
||||
}
|
||||
|
||||
// Redirect to mail page
|
||||
router.push('/mail');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle>Email Configuration</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="host">IMAP Host</Label>
|
||||
<Input
|
||||
id="host"
|
||||
type="text"
|
||||
value={host}
|
||||
onChange={(e) => setHost(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="port">IMAP Port</Label>
|
||||
<Input
|
||||
id="port"
|
||||
type="text"
|
||||
value={port}
|
||||
onChange={(e) => setPort(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="text-red-500 text-sm">{error}</div>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Connecting...' : 'Connect'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -39,7 +39,7 @@ import EmailList from '@/components/email/EmailList';
|
||||
import EmailSidebarContent from '@/components/email/EmailSidebarContent';
|
||||
import EmailDetailView from '@/components/email/EmailDetailView';
|
||||
import ComposeEmail from '@/components/email/ComposeEmail';
|
||||
import { DeleteConfirmDialog, LoginNeededAlert } from '@/components/email/EmailDialogs';
|
||||
import { DeleteConfirmDialog } from '@/components/email/EmailDialogs';
|
||||
|
||||
// Import the custom hook
|
||||
import { useCourrier, EmailData } from '@/hooks/use-courrier';
|
||||
@ -322,7 +322,6 @@ export default function CourrierPage() {
|
||||
} else {
|
||||
console.error('Max retries reached for session request');
|
||||
// Instead of throwing, redirect to login
|
||||
router.push('/courrier/login');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -595,9 +594,7 @@ export default function CourrierPage() {
|
||||
const handleMailboxChange = (folder: string, accountId?: string) => {
|
||||
if (accountId && accountId !== 'loading-account') {
|
||||
const account = accounts.find(a => a.id === accountId);
|
||||
|
||||
if (!account) {
|
||||
console.warn(`Account ${accountId} not found`);
|
||||
toast({
|
||||
title: "Account not found",
|
||||
description: `The account ${accountId} could not be found.`,
|
||||
@ -605,43 +602,18 @@ export default function CourrierPage() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the account has initialized folders
|
||||
if (!account.folders || account.folders.length === 0) {
|
||||
console.warn(`No folders initialized for account ${accountId}`);
|
||||
// Set default folders if none are initialized
|
||||
account.folders = ['INBOX', 'SENT', 'DRAFTS', 'TRASH'];
|
||||
}
|
||||
|
||||
// Use the full prefixed folder name for existence check
|
||||
const fullFolder = folder.includes(':') ? folder : `${accountId}:${folder}`;
|
||||
|
||||
if (!account.folders.includes(fullFolder)) {
|
||||
console.warn(`Folder ${fullFolder} not found in account ${accountId}, defaulting to INBOX`);
|
||||
// Only allow navigation to folders in selectedAccount.folders
|
||||
if (!account.folders.includes(folder)) {
|
||||
toast({
|
||||
title: "Folder not found",
|
||||
description: `The folder ${fullFolder} does not exist for this account. Defaulting to INBOX.`,
|
||||
description: `The folder ${folder} does not exist for this account.`,
|
||||
variant: "destructive",
|
||||
});
|
||||
// Default to INBOX with prefix
|
||||
setSelectedFolders(prev => ({
|
||||
...prev,
|
||||
[accountId]: `${accountId}:INBOX`
|
||||
}));
|
||||
changeFolder(`${accountId}:INBOX`, accountId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update selected folders state with the full prefixed folder name
|
||||
setSelectedFolders(prev => ({
|
||||
...prev,
|
||||
[accountId]: fullFolder
|
||||
}));
|
||||
|
||||
// Change to the selected folder with account prefix
|
||||
changeFolder(fullFolder, accountId);
|
||||
setSelectedFolders(prev => ({ ...prev, [accountId]: folder }));
|
||||
changeFolder(folder, accountId);
|
||||
} else {
|
||||
// For all accounts view, use the original folder name
|
||||
changeFolder(folder, accountId);
|
||||
}
|
||||
};
|
||||
@ -1046,7 +1018,6 @@ export default function CourrierPage() {
|
||||
>
|
||||
<div className={`w-3 h-3 rounded-full ${account.color?.startsWith('#') ? 'bg-blue-500' : account.color || 'bg-blue-500'} mr-2`}></div>
|
||||
<span className="truncate text-gray-700 flex-1">{account.name}</span>
|
||||
{/* More options button */}
|
||||
{account.id !== 'loading-account' && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@ -1071,16 +1042,12 @@ export default function CourrierPage() {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{/* Show folders for this account if expanded */}
|
||||
{(() => {
|
||||
const isExpanded = expandedAccounts[account.id];
|
||||
const hasFolders = account.folders && account.folders.length > 0;
|
||||
return isExpanded && account.id !== 'loading-account' && hasFolders && (
|
||||
<div className="pl-4">
|
||||
{account.folders.map((folder) => renderFolderButton(folder, account.id))}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{/* Show only selectedAccount.folders for the selected account if expanded */}
|
||||
{selectedAccount && account.id === selectedAccount.id && expandedAccounts[account.id] && account.folders && account.folders.length > 0 && (
|
||||
<div className="pl-4">
|
||||
{account.folders.map((folder) => renderFolderButton(folder, account.id))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -1161,16 +1128,6 @@ export default function CourrierPage() {
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>
|
||||
{error}
|
||||
{(error?.includes('Not authenticated') || error?.includes('No email credentials found')) && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
onClick={handleGoToLogin}
|
||||
>
|
||||
Go to login
|
||||
</Button>
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
@ -1260,12 +1217,6 @@ export default function CourrierPage() {
|
||||
onConfirm={handleDeleteConfirm}
|
||||
onCancel={() => setShowDeleteConfirm(false)}
|
||||
/>
|
||||
|
||||
<LoginNeededAlert
|
||||
show={showLoginNeeded}
|
||||
onLogin={handleGoToLogin}
|
||||
onClose={() => setShowLoginNeeded(false)}
|
||||
/>
|
||||
|
||||
{/* Compose Email Dialog */}
|
||||
<Dialog open={showComposeModal} onOpenChange={(open) => !open && setShowComposeModal(false)}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user