courrier preview
This commit is contained in:
parent
81ed21b6b4
commit
4a2297f4b0
@ -201,44 +201,86 @@ export async function DELETE(request: Request) {
|
|||||||
|
|
||||||
export async function PATCH(request: Request) {
|
export async function PATCH(request: Request) {
|
||||||
try {
|
try {
|
||||||
|
// Authenticate user
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
return NextResponse.json(
|
||||||
|
{ error: 'Unauthorized' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { accountId, newPassword } = body;
|
const { accountId, newPassword, display_name, color } = body;
|
||||||
if (!accountId || !newPassword) {
|
|
||||||
return NextResponse.json({ error: 'Missing accountId or newPassword' }, { status: 400 });
|
if (!accountId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Account ID is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Find the account
|
|
||||||
|
// Check if at least one of the fields is provided
|
||||||
|
if (!newPassword && !display_name && !color) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'At least one field to update is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the account belongs to the user
|
||||||
const account = await prisma.mailCredentials.findFirst({
|
const account = await prisma.mailCredentials.findFirst({
|
||||||
where: { id: accountId, userId: session.user.id },
|
where: {
|
||||||
|
id: accountId,
|
||||||
|
userId: session.user.id
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return NextResponse.json({ error: 'Account not found' }, { status: 404 });
|
return NextResponse.json(
|
||||||
|
{ error: 'Account not found' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Test new credentials
|
|
||||||
const testResult = await testEmailConnection({
|
// Build update data object
|
||||||
email: account.email,
|
const updateData: any = {};
|
||||||
password: newPassword,
|
|
||||||
host: account.host,
|
// Add password if provided
|
||||||
port: account.port,
|
if (newPassword) {
|
||||||
secure: typeof (account as any).secure === 'boolean' ? (account as any).secure : true,
|
updateData.password = newPassword;
|
||||||
});
|
|
||||||
if (!testResult.imap) {
|
|
||||||
return NextResponse.json({ error: `Connection test failed: ${testResult.error || 'Could not connect to IMAP server'}` }, { status: 400 });
|
|
||||||
}
|
}
|
||||||
// Update password in database
|
|
||||||
|
// Add display_name if provided
|
||||||
|
if (display_name !== undefined) {
|
||||||
|
updateData.display_name = display_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add color if provided
|
||||||
|
if (color) {
|
||||||
|
updateData.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the account
|
||||||
await prisma.mailCredentials.update({
|
await prisma.mailCredentials.update({
|
||||||
where: { id: accountId },
|
where: { id: accountId },
|
||||||
data: { password: newPassword },
|
data: updateData
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Account updated successfully'
|
||||||
});
|
});
|
||||||
// Invalidate cache
|
|
||||||
await invalidateFolderCache(session.user.id, account.email, '*');
|
|
||||||
return NextResponse.json({ success: true, message: 'Password updated' });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating account password:', error);
|
console.error('Error updating account:', error);
|
||||||
return NextResponse.json({ error: 'Failed to update password', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 });
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Failed to update account',
|
||||||
|
details: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight,
|
Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight,
|
||||||
Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll,
|
Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll,
|
||||||
MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
|
MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
|
||||||
AlertOctagon, Archive, RefreshCw, Menu
|
AlertOctagon, Archive, Menu, Check
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
@ -901,37 +901,109 @@ export default function CourrierPage() {
|
|||||||
|
|
||||||
{/* Edit Password Modal */}
|
{/* Edit Password Modal */}
|
||||||
<Dialog open={showEditModal} onOpenChange={open => { if (!open) setShowEditModal(false); }}>
|
<Dialog open={showEditModal} onOpenChange={open => { if (!open) setShowEditModal(false); }}>
|
||||||
<DialogContent className="sm:max-w-[400px]">
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
<DialogTitle>Edit Account Password</DialogTitle>
|
<DialogTitle>Edit Account Settings</DialogTitle>
|
||||||
<form onSubmit={async e => {
|
<form onSubmit={async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!accountToEdit) return;
|
if (!accountToEdit) return;
|
||||||
setEditLoading(true);
|
setEditLoading(true);
|
||||||
try {
|
try {
|
||||||
|
const formElement = e.target as HTMLFormElement;
|
||||||
|
const displayName = (formElement.querySelector('#display-name') as HTMLInputElement).value;
|
||||||
|
const color = (formElement.querySelector('input[name="color"]:checked') as HTMLInputElement)?.value || accountToEdit.color;
|
||||||
|
|
||||||
const res = await fetch('/api/courrier/account', {
|
const res = await fetch('/api/courrier/account', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ accountId: accountToEdit.id, newPassword }),
|
body: JSON.stringify({
|
||||||
|
accountId: accountToEdit.id,
|
||||||
|
newPassword: newPassword || undefined,
|
||||||
|
display_name: displayName,
|
||||||
|
color: color
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) throw new Error(data.error || 'Failed to update password');
|
if (!res.ok) throw new Error(data.error || 'Failed to update account settings');
|
||||||
toast({ title: 'Password updated', description: 'Password changed successfully.' });
|
toast({ title: 'Account updated', description: 'Account settings updated successfully.' });
|
||||||
setShowEditModal(false);
|
setShowEditModal(false);
|
||||||
setNewPassword('');
|
setNewPassword('');
|
||||||
window.location.reload();
|
// Update the local account data
|
||||||
|
setAccounts(accounts.map(account =>
|
||||||
|
account.id === accountToEdit.id
|
||||||
|
? {...account, name: displayName, color: color}
|
||||||
|
: account
|
||||||
|
));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({ title: 'Error', description: err instanceof Error ? err.message : 'Failed to update password', variant: 'destructive' });
|
toast({ title: 'Error', description: err instanceof Error ? err.message : 'Failed to update account settings', variant: 'destructive' });
|
||||||
} finally {
|
} finally {
|
||||||
setEditLoading(false);
|
setEditLoading(false);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<div className="mb-2">
|
<div className="mb-4">
|
||||||
<Label htmlFor="new-password">New Password</Label>
|
<Label htmlFor="display-name">Account Name</Label>
|
||||||
<Input id="new-password" type="password" value={newPassword} onChange={e => setNewPassword(e.target.value)} required className="mt-1" />
|
<Input
|
||||||
|
id="display-name"
|
||||||
|
type="text"
|
||||||
|
defaultValue={accountToEdit?.name}
|
||||||
|
className="mt-1 bg-white text-gray-800"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2 mt-4">
|
|
||||||
<Button type="button" variant="outline" onClick={() => setShowEditModal(false)}>Cancel</Button>
|
<div className="mb-4">
|
||||||
<Button type="submit" disabled={editLoading}>{editLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : 'Save'}</Button>
|
<Label htmlFor="new-password">New Password (optional)</Label>
|
||||||
|
<Input
|
||||||
|
id="new-password"
|
||||||
|
type="password"
|
||||||
|
value={newPassword}
|
||||||
|
onChange={e => setNewPassword(e.target.value)}
|
||||||
|
className="mt-1 bg-white text-gray-800"
|
||||||
|
placeholder="Leave blank to keep current password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<Label className="block mb-2">Account Color</Label>
|
||||||
|
<div className="grid grid-cols-5 gap-2">
|
||||||
|
{colorPalette.map((color, index) => (
|
||||||
|
<div key={index} className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id={`color-${index}`}
|
||||||
|
name="color"
|
||||||
|
value={color}
|
||||||
|
defaultChecked={accountToEdit?.color === color}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`color-${index}`}
|
||||||
|
className={`w-8 h-8 rounded-full cursor-pointer flex items-center justify-center ${color} hover:ring-2 hover:ring-blue-300 transition-all`}
|
||||||
|
style={{ boxShadow: accountToEdit?.color === color ? '0 0 0 2px white, 0 0 0 4px #3b82f6' : 'none' }}
|
||||||
|
>
|
||||||
|
{accountToEdit?.color === color && (
|
||||||
|
<Check className="h-4 w-4 text-white" />
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 mt-6">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="bg-red-500 hover:bg-red-600 text-white"
|
||||||
|
onClick={() => setShowEditModal(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="bg-blue-500 hover:bg-blue-600 text-white"
|
||||||
|
disabled={editLoading}
|
||||||
|
>
|
||||||
|
{editLoading ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : null}
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user