"use client" import { useState, useEffect } from "react" import { useTranslations } from "next-intl" import { Share2, Copy, Trash2, Link2 } from "lucide-react" import { cn } from "@/lib/utils" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { useToast } from "@/components/ui/use-toast" import { useCopy } from "@/hooks/use-copy" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { EXPIRY_OPTIONS } from "@/types/email" interface ShareMessageDialogProps { emailId: string messageId: string messageSubject: string trigger?: React.ReactNode } interface ShareLink { id: string token: string createdAt: number | string | Date expiresAt: number | string | Date | null enabled: boolean } export function ShareMessageDialog({ emailId, messageId, messageSubject, trigger }: ShareMessageDialogProps) { const t = useTranslations("emails.shareMessage") const { toast } = useToast() const { copyToClipboard } = useCopy() const [open, setOpen] = useState(false) const [shares, setShares] = useState([]) const [loading, setLoading] = useState(false) const [creating, setCreating] = useState(false) const [expiryTime, setExpiryTime] = useState(EXPIRY_OPTIONS[1].value.toString()) const [deleteTarget, setDeleteTarget] = useState(null) const fetchShares = async () => { try { setLoading(true) const response = await fetch(`/api/emails/${emailId}/messages/${messageId}/share`) if (!response.ok) throw new Error("Failed to fetch shares") const data = await response.json() as { shares: ShareLink[] } setShares(data.shares || []) } catch (error) { console.error("Failed to fetch shares:", error) toast({ title: t("createFailed"), description: String(error), variant: "destructive" }) } finally { setLoading(false) } } const createShare = async () => { try { setCreating(true) const response = await fetch(`/api/emails/${emailId}/messages/${messageId}/share`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ expiresIn: Number(expiryTime) }) }) if (!response.ok) throw new Error("Failed to create share") const share = await response.json() as ShareLink setShares(prev => [share, ...prev]) toast({ title: t("createSuccess"), }) } catch (error) { console.error("Failed to create share:", error) toast({ title: t("createFailed"), description: String(error), variant: "destructive" }) } finally { setCreating(false) } } const deleteShare = async (share: ShareLink) => { try { const response = await fetch(`/api/emails/${emailId}/messages/${messageId}/share/${share.id}`, { method: "DELETE" }) if (!response.ok) throw new Error("Failed to delete share") setShares(prev => prev.filter(s => s.id !== share.id)) toast({ title: t("deleteSuccess"), }) } catch (error) { console.error("Failed to delete share:", error) toast({ title: t("deleteFailed"), description: String(error), variant: "destructive" }) } finally { setDeleteTarget(null) } } const getShareUrl = (token: string) => { return `${window.location.origin}/shared/message/${token}` } const handleCopy = async (token: string) => { const url = getShareUrl(token) const success = await copyToClipboard(url) if (success) { toast({ title: t("copied"), }) } else { toast({ title: t("copyFailed"), variant: "destructive" }) } } useEffect(() => { if (open) { fetchShares() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]) return ( <> {trigger || ( )} e.preventDefault()} onEscapeKeyDown={(e) => { if (deleteTarget) { e.preventDefault() } }} > {t("title")} {t("description")}
{/* Message info */}

{messageSubject}

{/* Create new share link */}
{/* Active share links */}
{loading ? (
{t("loading")}
) : shares.length === 0 ? (
{t("noLinks")}
) : (
{shares.map(share => { // 将expiresAt转换为时间戳进行比较 const expiresAtTime = share.expiresAt ? (typeof share.expiresAt === 'number' ? share.expiresAt : new Date(share.expiresAt).getTime()) : null const isExpired = expiresAtTime !== null && expiresAtTime < Date.now() return (
{t("createdAt")}: {new Date( typeof share.createdAt === 'number' ? share.createdAt : share.createdAt ).toLocaleString()} {t("expiresAt")}: { share.expiresAt ? new Date( typeof share.expiresAt === 'number' ? share.expiresAt : share.expiresAt ).toLocaleString() : t("permanent") } {isExpired && ( {t("expired")} )}
) })}
)}
setDeleteTarget(null)}> {t("deleteConfirm")} {t("deleteDescription")} {t("cancel")} deleteTarget && deleteShare(deleteTarget)} > {t("delete")} ) }