feat: add delete function for single email message

This commit is contained in:
jieyitang
2025-03-14 16:52:38 +08:00
parent eb8023280b
commit 21d09a2cb0
2 changed files with 136 additions and 3 deletions
+50
View File
@@ -5,6 +5,56 @@ import { and, eq } from "drizzle-orm"
import { getUserId } from "@/lib/apiKey" import { getUserId } from "@/lib/apiKey"
export const runtime = "edge" export const runtime = "edge"
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string; messageId: string }> }
) {
const userId = await getUserId()
try {
const db = createDb()
const { id, messageId } = await params
const email = await db.query.emails.findFirst({
where: and(
eq(emails.id, id),
eq(emails.userId, userId!)
)
})
if (!email) {
return NextResponse.json(
{ error: "Email not found or no permission to view" },
{ status: 403 }
)
}
const message = await db.query.messages.findFirst({
where: and(
eq(messages.emailId, id),
eq(messages.id, messageId)
)
})
if(!message) {
return NextResponse.json(
{ error: "Message not found or already deleted" },
{ status: 404 }
)
}
await db.delete(messages)
.where(eq(messages.id, messageId))
return NextResponse.json({ success: true })
} catch (error) {
console.error('Failed to delete email:', error)
return NextResponse.json(
{ error: "Failed to delete message" },
{ status: 500 }
)
}
}
export async function GET(_request: Request, { params }: { params: Promise<{ id: string; messageId: string }> }) { export async function GET(_request: Request, { params }: { params: Promise<{ id: string; messageId: string }> }) {
try { try {
const { id, messageId } = await params const { id, messageId } = await params
+86 -3
View File
@@ -1,11 +1,22 @@
"use client" "use client"
import { useState, useEffect, useRef } from "react" import { useState, useEffect, useRef } from "react"
import { Mail, Calendar, RefreshCw } from "lucide-react" import {Mail, Calendar, RefreshCw, Trash2} from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { useThrottle } from "@/hooks/use-throttle" import { useThrottle } from "@/hooks/use-throttle"
import { EMAIL_CONFIG } from "@/config" import { EMAIL_CONFIG } from "@/config"
import { useToast } from "@/components/ui/use-toast"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from "@/components/ui/alert-dialog";
interface Message { interface Message {
id: string id: string
@@ -19,7 +30,7 @@ interface MessageListProps {
id: string id: string
address: string address: string
} }
onMessageSelect: (messageId: string) => void onMessageSelect: (messageId: string | null) => void
selectedMessageId?: string | null selectedMessageId?: string | null
} }
@@ -38,6 +49,8 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa
const pollTimeoutRef = useRef<Timer>() const pollTimeoutRef = useRef<Timer>()
const messagesRef = useRef<Message[]>([]) // 添加 ref 来追踪最新的消息列表 const messagesRef = useRef<Message[]>([]) // 添加 ref 来追踪最新的消息列表
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
const [messageToDelete, setMessageToDelete] = useState<Message | null>(null)
const { toast } = useToast()
// 当 messages 改变时更新 ref // 当 messages 改变时更新 ref
useEffect(() => { useEffect(() => {
@@ -118,6 +131,44 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa
} }
}, 200) }, 200)
const handleDelete = async (message: Message) => {
try {
const response = await fetch(`/api/emails/${email.id}/${message.id}`, {
method: "DELETE"
})
if (!response.ok) {
const data = await response.json()
toast({
title: "错误",
description: (data as { error: string }).error,
variant: "destructive"
})
return
}
setMessages(prev => prev.filter(e => e.id !== message.id))
setTotal(prev => prev - 1)
toast({
title: "成功",
description: "邮件已删除"
})
if (selectedMessageId === message.id) {
onMessageSelect(null)
}
} catch {
toast({
title: "错误",
description: "删除邮件失败",
variant: "destructive"
})
} finally {
setMessageToDelete(null)
}
}
useEffect(() => { useEffect(() => {
if (!email.id) { if (!email.id) {
return return
@@ -134,6 +185,7 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa
}, [email.id]) }, [email.id])
return ( return (
<>
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="p-2 flex justify-between items-center border-b border-primary/20"> <div className="p-2 flex justify-between items-center border-b border-primary/20">
<Button <Button
@@ -160,7 +212,7 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa
key={message.id} key={message.id}
onClick={() => onMessageSelect(message.id)} onClick={() => onMessageSelect(message.id)}
className={cn( className={cn(
"p-3 hover:bg-primary/5 cursor-pointer", "p-3 hover:bg-primary/5 cursor-pointer group",
selectedMessageId === message.id && "bg-primary/10" selectedMessageId === message.id && "bg-primary/10"
)} )}
> >
@@ -176,6 +228,17 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa
</span> </span>
</div> </div>
</div> </div>
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100 h-8 w-8"
onClick={(e) => {
e.stopPropagation()
setMessageToDelete(message)
}}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div> </div>
</div> </div>
))} ))}
@@ -192,5 +255,25 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa
)} )}
</div> </div>
</div> </div>
<AlertDialog open={!!messageToDelete} onOpenChange={() => setMessageToDelete(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
{messageToDelete?.subject}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
className="bg-destructive hover:bg-destructive/90"
onClick={() => messageToDelete && handleDelete(messageToDelete)}
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
) )
} }