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 }
|
{ 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 { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { toast } from '@/components/ui/use-toast';
|
import { toast } from '@/components/ui/use-toast';
|
||||||
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu';
|
||||||
|
|
||||||
// Import components
|
// Import components
|
||||||
import EmailSidebar from '@/components/email/EmailSidebar';
|
import EmailSidebar from '@/components/email/EmailSidebar';
|
||||||
@ -163,6 +164,15 @@ export default function CourrierPage() {
|
|||||||
// Track folder visibility per account
|
// Track folder visibility per account
|
||||||
const [visibleFolders, setVisibleFolders] = useState<Record<string, string[]>>({});
|
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
|
// Debug accounts state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Current accounts state:', accounts);
|
console.log('Current accounts state:', accounts);
|
||||||
@ -1024,17 +1034,24 @@ export default function CourrierPage() {
|
|||||||
<div className="flex items-center w-full">
|
<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>
|
<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>
|
<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' && (
|
{account.id !== 'all-accounts' && (
|
||||||
<span
|
<span
|
||||||
className="ml-auto text-gray-400 hover:text-gray-600 cursor-pointer"
|
className="ml-1 text-gray-400 hover:text-gray-600 cursor-pointer"
|
||||||
onClick={(e) => {
|
onClick={e => { e.stopPropagation(); setExpandedAccounts(prev => ({ ...prev, [account.id]: !prev[account.id] })); }}
|
||||||
e.stopPropagation();
|
|
||||||
// Toggle this specific account's expanded state
|
|
||||||
setExpandedAccounts(prev => ({
|
|
||||||
...prev,
|
|
||||||
[account.id]: !prev[account.id]
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{expandedAccounts[account.id] ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
{expandedAccounts[account.id] ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
||||||
</span>
|
</span>
|
||||||
@ -1259,6 +1276,52 @@ export default function CourrierPage() {
|
|||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</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