feat: implement email sending functionality via Resend service

This commit is contained in:
beilunyang
2025-06-21 23:50:46 +08:00
parent 9d55564073
commit e85f6b04bd
27 changed files with 2347 additions and 467 deletions

View File

@@ -5,41 +5,72 @@ import { Loader2 } from "lucide-react"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Label } from "@/components/ui/label"
import { useTheme } from "next-themes"
import { useToast } from "@/components/ui/use-toast"
interface Message {
id: string
from_address: string
from_address?: string
to_address?: string
subject: string
content: string
html: string | null
received_at: number
html?: string
received_at?: number
sent_at?: number
}
interface MessageViewProps {
emailId: string
messageId: string
messageType?: 'received' | 'sent'
onClose: () => void
}
type ViewMode = "html" | "text"
export function MessageView({ emailId, messageId }: MessageViewProps) {
export function MessageView({ emailId, messageId, messageType = 'received' }: MessageViewProps) {
const [message, setMessage] = useState<Message | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [viewMode, setViewMode] = useState<ViewMode>("html")
const iframeRef = useRef<HTMLIFrameElement>(null)
const { theme } = useTheme()
const { toast } = useToast()
useEffect(() => {
const fetchMessage = async () => {
try {
const response = await fetch(`/api/emails/${emailId}/${messageId}`)
setLoading(true)
setError(null)
const url = `/api/emails/${emailId}/${messageId}${messageType === 'sent' ? '?type=sent' : ''}`;
const response = await fetch(url)
if (!response.ok) {
const errorData = await response.json()
const errorMessage = (errorData as { error?: string }).error || '获取邮件详情失败'
setError(errorMessage)
toast({
title: "错误",
description: errorMessage,
variant: "destructive"
})
return
}
const data = await response.json() as { message: Message }
setMessage(data.message)
if (!data.message.html) {
setViewMode("text")
}
} catch (error) {
const errorMessage = "网络错误,请稍后重试"
setError(errorMessage)
toast({
title: "错误",
description: errorMessage,
variant: "destructive"
})
console.error("Failed to fetch message:", error)
} finally {
setLoading(false)
@@ -47,7 +78,7 @@ export function MessageView({ emailId, messageId }: MessageViewProps) {
}
fetchMessage()
}, [emailId, messageId])
}, [emailId, messageId, messageType, toast])
const updateIframeContent = () => {
if (viewMode === "html" && message?.html && iframeRef.current) {
@@ -151,6 +182,21 @@ export function MessageView({ emailId, messageId }: MessageViewProps) {
return (
<div className="flex items-center justify-center h-32">
<Loader2 className="w-5 h-5 animate-spin text-primary/60" />
<span className="ml-2 text-sm text-gray-500">...</span>
</div>
)
}
if (error) {
return (
<div className="flex flex-col items-center justify-center h-32 text-center">
<p className="text-sm text-destructive mb-2">{error}</p>
<button
onClick={() => window.location.reload()}
className="text-xs text-primary hover:underline"
>
</button>
</div>
)
}
@@ -162,12 +208,17 @@ export function MessageView({ emailId, messageId }: MessageViewProps) {
<div className="p-4 space-y-3 border-b border-primary/20">
<h3 className="text-base font-bold">{message.subject}</h3>
<div className="text-xs text-gray-500 space-y-1">
<p>{message.from_address}</p>
<p>{new Date(message.received_at).toLocaleString()}</p>
{message.from_address && (
<p>{message.from_address}</p>
)}
{message.to_address && (
<p>{message.to_address}</p>
)}
<p>{new Date(message.sent_at || message.received_at || 0).toLocaleString()}</p>
</div>
</div>
{message.html && (
{message.html && message.content && (
<div className="border-b border-primary/20 p-2">
<RadioGroup
value={viewMode}