"use client"; import { useEffect, useMemo, 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 { useTranslations } from "next-intl"; import { toast } from "sonner"; import useSWR from "swr"; import { UserEmailList } from "@/lib/dto/email"; import { reservedAddressSuffix } from "@/lib/enums"; import { cn, fetcher, nFormatter } 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 { TimeAgoIntl } from "../shared/time-ago"; 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 t = useTranslations("Email"); 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(null); 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; min_email_length: number }[] >("/api/domain?feature=email", fetcher, { revalidateOnFocus: false, dedupingInterval: 10000, }); useEffect(() => { if (!domainSuffix && emailDomains && emailDomains.length > 0) { setDomainSuffix(emailDomains[0].domain_name); } }, [domainSuffix, emailDomains]); 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) => { const limit_len = emailDomains?.find((d) => d.domain_name === domainSuffix) ?.min_email_length ?? 1; if (!emailSuffix || emailSuffix.length < limit_len) { toast.error(`Email address characters must be at least ${limit_len}`); 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={t("Search emails")} className="h-[30px] w-full border p-1 pl-8 text-xs placeholder:text-xs" />
)}
{!isCollapsed && (
{/* Address */}

{t("Email Address")}

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

{/* Inbox Emails */}

{t("Inbox Emails")}

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

{ setOnlyUnread(!onlyUnread); }} >

{t("Unread Emails")}

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

{t("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, }, )} >

{t("Sent Emails")}

{nFormatter(sendEmails || 0)}

)} {!isCollapsed && user.role === "ADMIN" && (
{t("Admin Mode")}:{" "} setAdminModel(v)} />
)}
{isLoading && (
)} {error && (
)} {!error && !isLoading && userEmails && userEmails.length === 0 && ( <> {!isCollapsed ? (
{t("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} )} {t("{email} recived", { email: email.count })}
{isAdminModel ? `${email.user || email.email.slice(0, 5)} · ` : ""}
)}
))}
{/* Pagination */} {!isCollapsed && data && totalPages > 1 && ( )} {showSendsModal && ( )} {/* 创建\编辑邮箱的 Modal */} {showEmailModal && (

{isEdit ? t("Edit email") : t("Create new email")}

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

{t("Delete email")}

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

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

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