"use client"; import { useState, useEffect, useRef, useMemo } from "react"; import { useTranslations } from "next-intl"; import { X, ShieldCheck, Search, Trash2, Plus, Loader2 } from "lucide-react"; import { Avatar } from "@/components/ui/avatar"; import { useSettingsStore } from "@/stores/settings-store"; import { useContactStore } from "@/stores/contact-store"; import { useAuthStore } from "@/stores/auth-store"; import { cn } from "@/lib/utils"; interface TrustedSendersModalProps { isOpen: boolean; onClose: () => void; } export function TrustedSendersModal({ isOpen, onClose }: TrustedSendersModalProps) { const t = useTranslations("settings.email_behavior.trusted_senders"); const modalRef = useRef(null); const inputRef = useRef(null); const { trustedSenders, addTrustedSender, removeTrustedSender, trustedSendersAddressBook } = useSettingsStore(); const { trustedSenderEmails, trustedSendersLoaded, trustedSendersLoading, loadTrustedSendersBook, addToTrustedSendersBook, removeFromTrustedSendersBook, } = useContactStore(); const { client } = useAuthStore(); const [searchQuery, setSearchQuery] = useState(""); const [isAdding, setIsAdding] = useState(false); const [newEmail, setNewEmail] = useState(""); const [emailError, setEmailError] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); // When address book mode is on, load the book on first open useEffect(() => { if (isOpen && trustedSendersAddressBook && client && !trustedSendersLoaded) { loadTrustedSendersBook(client); } }, [isOpen, trustedSendersAddressBook, client, trustedSendersLoaded, loadTrustedSendersBook]); // The active list depends on mode const activeSenders = trustedSendersAddressBook ? trustedSenderEmails : trustedSenders; const isLoading = trustedSendersAddressBook && (!trustedSendersLoaded || trustedSendersLoading); // Filter senders based on search query const filteredSenders = useMemo(() => { if (!searchQuery.trim()) return activeSenders; const query = searchQuery.toLowerCase(); return activeSenders.filter((email) => email.toLowerCase().includes(query)); }, [activeSenders, searchQuery]); // Show search only when 5+ senders const showSearch = activeSenders.length >= 5; // Close on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { if (isAdding) { setIsAdding(false); setNewEmail(""); setEmailError(""); } else { onClose(); } } }; if (isOpen) { window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); } }, [isOpen, isAdding, onClose]); // Close on click outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (modalRef.current && !modalRef.current.contains(e.target as Node)) { onClose(); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); } }, [isOpen, onClose]); // Focus input when adding mode is enabled useEffect(() => { if (isAdding && inputRef.current) { inputRef.current.focus(); } }, [isAdding]); // Reset state when modal closes useEffect(() => { if (!isOpen) { setSearchQuery(""); setIsAdding(false); setNewEmail(""); setEmailError(""); } }, [isOpen]); const validateEmail = (email: string): boolean => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; const handleAddSender = async () => { const trimmedEmail = newEmail.trim().toLowerCase(); if (!trimmedEmail) { setEmailError(t("invalid_email")); return; } if (!validateEmail(trimmedEmail)) { setEmailError(t("invalid_email")); return; } if (activeSenders.includes(trimmedEmail)) { setEmailError(t("already_added")); return; } setIsSubmitting(true); try { if (trustedSendersAddressBook && client) { await addToTrustedSendersBook(client, trimmedEmail); } else { addTrustedSender(trimmedEmail); } setNewEmail(""); setIsAdding(false); setEmailError(""); } catch { setEmailError(t("save_error")); } finally { setIsSubmitting(false); } }; const handleRemoveSender = async (email: string) => { if (trustedSendersAddressBook && client) { await removeFromTrustedSendersBook(client, email); } else { removeTrustedSender(email); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { handleAddSender(); } }; if (!isOpen) return null; return (
{/* Header */}

{t("modal_title")}

{/* Search (only when 5+ senders) */} {showSearch && (
setSearchQuery(e.target.value)} className="w-full ps-9 pe-3 py-2 text-sm bg-muted border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50" />
)} {/* Content */}
{isLoading ? (
) : activeSenders.length === 0 ? ( /* Empty State */

{t("empty_title")}

{t("empty_description")}

) : filteredSenders.length === 0 ? ( /* No search results */

{t("no_results")}

) : ( /* Sender list */
{filteredSenders.map((email) => (
{email}
))}
)}
{/* Footer - Add sender */} {!isLoading && activeSenders.length > 0 && (
{isAdding ? (
{ setNewEmail(e.target.value); setEmailError(""); }} onKeyDown={handleKeyDown} className={cn( "flex-1 px-3 py-2 text-sm bg-background border rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50", emailError ? "border-destructive" : "border-border" )} />
{emailError && (

{emailError}

)}
) : ( )}
)}
); }