"use client"; import { useState, useTransition } from "react"; import { User, UserEmail } from "@prisma/client"; import randomName from "@scaleway/random-name"; import { PanelLeftClose, PanelRightClose, PenLine, Search, Sparkles, SquarePlus, } from "lucide-react"; import { toast } from "sonner"; import useSWR from "swr"; import { UserEmailList } from "@/lib/dto/email"; import { reservedAddressSuffix } from "@/lib/enums"; import { cn, fetcher, nFormatter, timeAgo } from "@/lib/utils"; import { useMediaQuery } from "@/hooks/use-media-query"; import { CopyButton } from "../shared/copy-button"; import { EmptyPlaceholder } from "../shared/empty-placeholder"; import { Icons } from "../shared/icons"; import { PaginationWrapper } from "../shared/pagination"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; import { Input } from "../ui/input"; import { Modal } from "../ui/modal"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../ui/select"; import { Skeleton } from "../ui/skeleton"; import { Switch } from "../ui/switch"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "../ui/tooltip"; import { SendEmailModal } from "./SendEmailModal"; import SendsEmailList from "./SendsEmailList"; interface EmailSidebarProps { user: User; onSelectEmail: (emailAddress: string | null) => void; selectedEmailAddress: string | null; className?: string; isCollapsed?: boolean; setIsCollapsed: (isCollapsed: boolean) => void; isAdminModel: boolean; setAdminModel: (isAdminModel: boolean) => void; } export default function EmailSidebar({ user, onSelectEmail, selectedEmailAddress, className, isCollapsed, setIsCollapsed, isAdminModel, setAdminModel, }: EmailSidebarProps) { const { isMobile } = useMediaQuery(); const [isRefreshing, setIsRefreshing] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [isEdit, setIsEdit] = useState(false); const [showEmailModal, setShowEmailModal] = useState(false); const [isPending, startTransition] = useTransition(); const [domainSuffix, setDomainSuffix] = useState("wr.do"); const [showDeleteModal, setShowDeleteModal] = useState(false); const [emailToDelete, setEmailToDelete] = useState(null); const [deleteInput, setDeleteInput] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [onlyUnread, setOnlyUnread] = useState(false); const [showSendsModal, setShowSendsModal] = useState(false); const [pageSize, setPageSize] = useState(10); const { data, isLoading, error, mutate } = useSWR<{ list: UserEmailList[]; total: number; totalInboxCount: number; totalUnreadCount: number; }>( `/api/email?page=${currentPage}&size=${pageSize}&search=${searchQuery}&all=${isAdminModel}&unread=${onlyUnread}`, fetcher, { dedupingInterval: 5000 }, ); const { data: sendEmails } = useSWR( `/api/email/send?all=${isAdminModel}`, fetcher, { dedupingInterval: 5000, }, ); const { data: emailDomains, isLoading: isLoadingDomains } = useSWR< { domain_name: string }[] >("/api/domain?feature=email", fetcher, { revalidateOnFocus: false, dedupingInterval: 10000, }); if (!selectedEmailAddress && data && data.list.length > 0) { onSelectEmail(data.list[0].emailAddress); } const userEmails = data?.list || []; const totalPages = data ? Math.ceil(data.total / pageSize) : 0; const handleSubmitEmail = async (emailSuffix: string) => { if (!emailSuffix || emailSuffix.length < 5) { toast.error("Email address characters must be at least 5"); return; } if (/[^a-zA-Z0-9_\-\.]/.test(emailSuffix)) { toast.error("Invalid email address"); return; } if (!domainSuffix) { toast.error("Domain suffix cannot be empty"); return; } if (reservedAddressSuffix.includes(emailSuffix)) { toast.error("Email address is reserved, please choose another one"); return; } startTransition(async () => { if (isEdit) { const editEmailId = userEmails.find( (email) => email.emailAddress === selectedEmailAddress, )?.id; const res = await fetch(`/api/email/${editEmailId}`, { method: "PUT", body: JSON.stringify({ emailAddress: `${emailSuffix}@${domainSuffix}`, }), }); if (res.ok) { mutate(); setShowEmailModal(false); toast.success("Email updated successfully"); } else { toast.error("Failed to update email", { description: await res.text(), }); } return; } else { try { const res = await fetch("/api/email", { method: "POST", body: JSON.stringify({ emailAddress: `${emailSuffix}@${domainSuffix}`, }), }); if (res.ok) { mutate(); setShowEmailModal(false); toast.success("Email created successfully"); } else { toast.error("Failed to create email", { description: await res.text(), }); } } catch (error) { console.log("Error creating email:", error); toast.error("Error creating email"); } } }); }; const handleOpenEditEmail = async (email: UserEmail) => { onSelectEmail(email.emailAddress); setDomainSuffix(email.emailAddress.split("@")[1]); if (selectedEmailAddress === email.emailAddress) { setIsEdit(true); setShowEmailModal(true); } }; const handleDeleteEmail = async (id: string) => { startTransition(async () => { try { const res = await fetch(`/api/email/${id}`, { method: "DELETE", }); if (res.ok) { mutate(); setShowDeleteModal(false); setDeleteInput(""); setEmailToDelete(null); toast.success("Email deleted successfully"); } else { toast.error("Failed to delete email"); } } catch (error) { console.log("Error deleting email:", error); } }); }; const confirmDelete = () => { if (!emailToDelete) return; const selectedEmail = userEmails.find( (email) => email.id === emailToDelete, ); if (!selectedEmail) return; const expectedInput = `delete ${selectedEmail.emailAddress}`; if (deleteInput === expectedInput) { handleDeleteEmail(emailToDelete); } else { toast.error("Input does not match. Please type correctly."); } }; return (
{/* Header */}
{!isCollapsed && (
setSearchQuery(e.target.value)} placeholder="Search emails..." className="h-[30px] w-full border p-1 pl-8 text-xs placeholder:text-xs" />
)}
{!isCollapsed && (
{/* Address */}

Email Address

{nFormatter(data ? data.total : 0)}

{/* Inbox Emails */}

Inbox Emails

{nFormatter(data ? data.totalInboxCount : 0)}

{ setOnlyUnread(!onlyUnread); }} >

Unread Emails

{nFormatter(data ? data.totalUnreadCount : 0)}

Filter unread emails
{/* Sent Emails */}
setShowSendsModal(!showSendsModal)} className={cn( "flex cursor-pointer flex-col items-center gap-1 rounded-md bg-neutral-100 px-1 pb-1 pt-2 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700", { "bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700": showSendsModal, }, )} >

Sent Emails

{nFormatter(sendEmails || 0)}

)} {!isCollapsed && user.role === "ADMIN" && (
Admin Mode:{" "} setAdminModel(v)} />
)}
{isLoading && (
)} {error && (
)} {!error && !isLoading && userEmails && userEmails.length === 0 && ( <> {!isCollapsed ? (
No emails You don't have any email yet. Start creating email.
) : (
)} )} {userEmails.map((email) => (
onSelectEmail(email.emailAddress)} className={cn( `border-gray-5 group m-1 cursor-pointer bg-neutral-50 p-2 transition-colors hover:bg-neutral-100 dark:border-zinc-700 dark:bg-neutral-800 dark:hover:bg-neutral-900`, selectedEmailAddress === email.emailAddress ? "bg-gray-100 dark:bg-neutral-900" : "", isCollapsed ? "flex items-center justify-center" : "", )} >
{isCollapsed ? email.emailAddress.slice(0, 1).toLocaleUpperCase() : email.emailAddress} {!isCollapsed && ( <> } /> handleOpenEditEmail(email)} /> { if (!email.deletedAt) { setEmailToDelete(email.id); setShowDeleteModal(true); } }} /> )}
{!isCollapsed && (
{email.unreadCount > 0 && ( {email.unreadCount} )} {email.count} recived
{isAdminModel ? `Created by ${email.user || email.email.slice(0, 5)} at` : ""}{" "} {timeAgo(email.createdAt)}
)}
))}
{/* Pagination */} {!isCollapsed && data && totalPages > 1 && ( )} {showSendsModal && ( )} {/* 创建\编辑邮箱的 Modal */} {showEmailModal && (

{isEdit ? "Edit" : "Create new"} email

{ e.preventDefault(); const emailAddress = (e.target as any).emailAddress.value; handleSubmitEmail(emailAddress); }} >
{isLoadingDomains ? ( ) : ( )}
)} {/* 删除邮箱的 Modal */} {showDeleteModal && (

Delete email

You are about to delete the following email, once deleted, it cannot be recovered. All emails in inbox will be deleted at the same time. Are you sure you want to continue?

To confirm, please type{" "} delete{" "} {userEmails.find((e) => e.id === emailToDelete)?.emailAddress} {" "} below:

setDeleteInput(e.target.value)} placeholder={`please input`} className="mb-4" />
)}
); }