courrier multi account restore compose
This commit is contained in:
parent
c734f52444
commit
d3ff8e6b19
@ -156,4 +156,85 @@ export async function POST(request: Request) {
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
const { searchParams } = new URL(request.url);
|
||||
const accountId = searchParams.get('accountId');
|
||||
if (!accountId) {
|
||||
return NextResponse.json({ error: 'Missing accountId' }, { status: 400 });
|
||||
}
|
||||
// Find the account
|
||||
const account = await prisma.mailCredentials.findFirst({
|
||||
where: { id: accountId, userId: session.user.id },
|
||||
});
|
||||
if (!account) {
|
||||
return NextResponse.json({ error: 'Account not found' }, { status: 404 });
|
||||
}
|
||||
// Delete from database
|
||||
await prisma.mailCredentials.delete({ where: { id: accountId } });
|
||||
// Invalidate cache
|
||||
await invalidateFolderCache(session.user.id, account.email, '*');
|
||||
return NextResponse.json({ success: true, message: 'Account deleted' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting account:', error);
|
||||
return NextResponse.json({ error: 'Failed to delete account', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
const body = await request.json();
|
||||
const { accountId, newPassword } = body;
|
||||
if (!accountId || !newPassword) {
|
||||
return NextResponse.json({ error: 'Missing accountId or newPassword' }, { status: 400 });
|
||||
}
|
||||
// Find the account
|
||||
const account = await prisma.mailCredentials.findFirst({
|
||||
where: { id: accountId, userId: session.user.id },
|
||||
});
|
||||
if (!account) {
|
||||
return NextResponse.json({ error: 'Account not found' }, { status: 404 });
|
||||
}
|
||||
// Test new credentials
|
||||
const testResult = await testEmailConnection({
|
||||
email: account.email,
|
||||
password: newPassword,
|
||||
host: account.host,
|
||||
port: account.port,
|
||||
secure: typeof (account as any).secure === 'boolean' ? (account as any).secure : true,
|
||||
});
|
||||
if (!testResult.imap) {
|
||||
return NextResponse.json({ error: `Connection test failed: ${testResult.error || 'Could not connect to IMAP server'}` }, { status: 400 });
|
||||
}
|
||||
// Update password in database
|
||||
await prisma.mailCredentials.update({
|
||||
where: { id: accountId },
|
||||
data: { password: newPassword },
|
||||
});
|
||||
// Invalidate cache
|
||||
await invalidateFolderCache(session.user.id, account.email, '*');
|
||||
return NextResponse.json({ success: true, message: 'Password updated' });
|
||||
} catch (error) {
|
||||
console.error('Error updating account password:', error);
|
||||
return NextResponse.json({ error: 'Failed to update password', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddAccount = async (accountData: AccountData) => {
|
||||
// ... account creation logic ...
|
||||
// setAccounts(prev => [...prev, newAccount]);
|
||||
// setVisibleFolders(prev => ({
|
||||
// ...prev,
|
||||
// [newAccount.id]: newAccount.folders
|
||||
// }));
|
||||
};
|
||||
@ -31,6 +31,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu';
|
||||
|
||||
// Import components
|
||||
import EmailSidebar from '@/components/email/EmailSidebar';
|
||||
@ -163,6 +164,15 @@ export default function CourrierPage() {
|
||||
// Track folder visibility per account
|
||||
const [visibleFolders, setVisibleFolders] = useState<Record<string, string[]>>({});
|
||||
|
||||
// Add state for modals/dialogs
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [accountToEdit, setAccountToEdit] = useState<Account | null>(null);
|
||||
const [accountToDelete, setAccountToDelete] = useState<Account | null>(null);
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [editLoading, setEditLoading] = useState(false);
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
|
||||
// Debug accounts state
|
||||
useEffect(() => {
|
||||
console.log('Current accounts state:', accounts);
|
||||
@ -1024,17 +1034,24 @@ export default function CourrierPage() {
|
||||
<div className="flex items-center w-full">
|
||||
<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">{account.name}</span>
|
||||
{/* More options button */}
|
||||
{account.id !== 'all-accounts' && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="ml-1 h-5 w-5 p-0 text-gray-400 hover:text-gray-600" onClick={e => e.stopPropagation()}>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={e => { e.stopPropagation(); setAccountToEdit(account); setShowEditModal(true); }}>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={e => { e.stopPropagation(); setAccountToDelete(account); setShowDeleteDialog(true); }}>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
{account.id !== 'all-accounts' && (
|
||||
<span
|
||||
className="ml-auto text-gray-400 hover:text-gray-600 cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// Toggle this specific account's expanded state
|
||||
setExpandedAccounts(prev => ({
|
||||
...prev,
|
||||
[account.id]: !prev[account.id]
|
||||
}));
|
||||
}}
|
||||
className="ml-1 text-gray-400 hover:text-gray-600 cursor-pointer"
|
||||
onClick={e => { e.stopPropagation(); setExpandedAccounts(prev => ({ ...prev, [account.id]: !prev[account.id] })); }}
|
||||
>
|
||||
{expandedAccounts[account.id] ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
||||
</span>
|
||||
@ -1259,6 +1276,52 @@ export default function CourrierPage() {
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Edit Password Modal */}
|
||||
<Dialog open={showEditModal} onOpenChange={open => { if (!open) setShowEditModal(false); }}>
|
||||
<DialogContent className="sm:max-w-[400px]">
|
||||
<DialogTitle>Edit Account Password</DialogTitle>
|
||||
<form onSubmit={async e => {
|
||||
e.preventDefault();
|
||||
if (!accountToEdit) return;
|
||||
setEditLoading(true);
|
||||
try {
|
||||
const res = await fetch('/api/courrier/account', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ accountId: accountToEdit.id, newPassword }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || 'Failed to update password');
|
||||
toast({ title: 'Password updated', description: 'Password changed successfully.' });
|
||||
setShowEditModal(false);
|
||||
setNewPassword('');
|
||||
// Refresh accounts (re-fetch or reload page)
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
toast({ title: 'Error', description: err instanceof Error ? err.message : 'Failed to update password', variant: 'destructive' });
|
||||
} finally {
|
||||
setEditLoading(false);
|
||||
}
|
||||
}}>
|
||||
<div className="mb-2">
|
||||
<Label htmlFor="new-password">New Password</Label>
|
||||
<Input id="new-password" type="password" value={newPassword} onChange={e => setNewPassword(e.target.value)} required className="mt-1" />
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-4">
|
||||
<Button type="button" variant="outline" onClick={() => setShowEditModal(false)}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Account Dialog */}
|
||||
<AlertDialog open={showDeleteDialog} onOpenChange={open => { if (!open) setShowDeleteDialog(false); }}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Account</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete this account? This action cannot be undone.
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user