"use client"; import { useState, useEffect, useCallback, useRef, useMemo } from "react"; import { useRouter } from "@/i18n/navigation"; import { useTranslations } from "next-intl"; import { ArrowLeft, Upload, Download, Users, BookUser } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { useConfirmDialog } from "@/hooks/use-confirm-dialog"; import { ContactList } from "@/components/contacts/contact-list"; import { ContactDetail } from "@/components/contacts/contact-detail"; import { ContactForm } from "@/components/contacts/contact-form"; import { ContactGroupList } from "@/components/contacts/contact-group-list"; import { ContactGroupForm } from "@/components/contacts/contact-group-form"; import { ContactGroupDetail } from "@/components/contacts/contact-group-detail"; import { ContactImportDialog } from "@/components/contacts/contact-import-dialog"; import { exportContacts } from "@/components/contacts/contact-export"; import { useContactStore, getContactDisplayName } from "@/stores/contact-store"; import { useAuthStore } from "@/stores/auth-store"; import { useEmailStore } from "@/stores/email-store"; import { toast } from "@/stores/toast-store"; import { cn } from "@/lib/utils"; import { NavigationRail } from "@/components/layout/navigation-rail"; import { useIsMobile } from "@/hooks/use-media-query"; import type { ContactCard } from "@/lib/jmap/types"; type View = | "list" | "detail" | "create" | "edit" | "group-detail" | "group-create" | "group-edit" | "import" | "bulk-add-to-group"; export default function ContactsPage() { const router = useRouter(); const t = useTranslations("contacts"); const { client, isAuthenticated, logout, checkAuth, isLoading: authLoading } = useAuthStore(); const [initialCheckDone, setInitialCheckDone] = useState(() => useAuthStore.getState().isAuthenticated && !!useAuthStore.getState().client); const { quota, isPushConnected } = useEmailStore(); const { contacts, selectedContactId, searchQuery, supportsSync, activeTab, selectedContactIds, setSelectedContact, setSearchQuery, setActiveTab, fetchContacts, createContact, updateContact, deleteContact, addLocalContact, updateLocalContact, deleteLocalContact, getGroupMembers, createGroup, updateGroup, addMembersToGroup, removeMembersFromGroup, deleteGroup, toggleContactSelection, selectAllContacts, clearSelection, bulkDeleteContacts, bulkAddToGroup, importContacts, } = useContactStore(); const [view, setView] = useState("list"); const [selectedGroupId, setSelectedGroupId] = useState(null); const hasFetched = useRef(false); const { dialogProps: confirmDialogProps, confirm: confirmDialog } = useConfirmDialog(); const isMobile = useIsMobile(); // Check auth on mount useEffect(() => { checkAuth().finally(() => { setInitialCheckDone(true); }); }, [checkAuth]); useEffect(() => { if (initialCheckDone && !isAuthenticated && !authLoading) { try { sessionStorage.setItem('redirect_after_login', window.location.pathname); } catch { /* ignore */ } router.push("/login"); } }, [initialCheckDone, isAuthenticated, authLoading, router]); useEffect(() => { if (client && supportsSync && !hasFetched.current) { hasFetched.current = true; fetchContacts(client); } }, [client, supportsSync, fetchContacts]); const groups = useMemo(() => contacts.filter(c => c.kind === 'group'), [contacts]); const individuals = useMemo(() => contacts.filter(c => c.kind !== 'group'), [contacts]); const selectedContact = contacts.find((c) => c.id === selectedContactId) || null; const selectedGroup = selectedGroupId ? contacts.find(c => c.id === selectedGroupId) || null : null; const selectedGroupMembers = selectedGroupId ? getGroupMembers(selectedGroupId) : []; const handleSelectContact = (id: string) => { setSelectedContact(id); clearSelection(); setView("detail"); }; const handleCreateNew = () => { setSelectedContact(null); setView("create"); }; const handleEdit = () => { setView("edit"); }; const handleDelete = async () => { if (!selectedContact) return; const confirmed = await confirmDialog({ title: t("delete_confirm_title"), message: t("delete_confirm"), confirmText: t("form.delete"), variant: "destructive", }); if (!confirmed) return; try { if (supportsSync && client) { await deleteContact(client, selectedContact.id); } else { deleteLocalContact(selectedContact.id); } toast.success(t("toast.deleted")); setView("list"); } catch (error) { console.error('Failed to delete contact:', error); toast.error(t("toast.error_delete")); } }; const handleSaveNew = useCallback(async (data: Partial) => { if (supportsSync && client) { await createContact(client, data); toast.success(t("toast.created")); } else { const localContact: ContactCard = { id: `local-${crypto.randomUUID()}`, addressBookIds: {}, ...data, }; addLocalContact(localContact); toast.success(t("toast.created")); } setView("list"); }, [supportsSync, client, createContact, addLocalContact, t]); const handleSaveEdit = useCallback(async (data: Partial) => { if (!selectedContact) return; if (supportsSync && client) { await updateContact(client, selectedContact.id, data); toast.success(t("toast.updated")); } else { updateLocalContact(selectedContact.id, data); toast.success(t("toast.updated")); } setView("detail"); }, [supportsSync, client, selectedContact, updateContact, updateLocalContact, t]); const handleCancel = () => { if (view === "group-create" || view === "group-edit") { setView(selectedGroup ? "group-detail" : "list"); } else if (view === "import") { setView("list"); } else if (view === "bulk-add-to-group") { setView("list"); } else { setView(selectedContact ? "detail" : "list"); } }; const handleSelectGroup = (id: string) => { setSelectedGroupId(id); setView("group-detail"); }; const handleCreateGroup = () => { setSelectedGroupId(null); setView("group-create"); }; const handleEditGroup = () => { setView("group-edit"); }; const handleDeleteGroup = async () => { if (!selectedGroup) return; const confirmed = await confirmDialog({ title: t("groups.delete_confirm_title"), message: t("groups.delete_confirm"), confirmText: t("form.delete"), variant: "destructive", }); if (!confirmed) return; try { await deleteGroup(supportsSync && client ? client : null, selectedGroup.id); toast.success(t("toast.deleted")); setSelectedGroupId(null); setView("list"); } catch (error) { console.error('Failed to delete group:', error); toast.error(t("toast.error_delete")); } }; const handleSaveGroup = useCallback(async (name: string, memberIds: string[]) => { const jmapClient = supportsSync && client ? client : null; if (view === "group-edit" && selectedGroup) { await updateGroup(jmapClient, selectedGroup.id, name); // Use resolved member contact IDs for diff, not raw urn:uuid: keys const currentIds = selectedGroupMembers.map(m => m.id); const toAdd = memberIds.filter(id => !currentIds.includes(id)); const toRemove = currentIds.filter(id => !memberIds.includes(id)); if (toAdd.length > 0) await addMembersToGroup(jmapClient, selectedGroup.id, toAdd); if (toRemove.length > 0) await removeMembersFromGroup(jmapClient, selectedGroup.id, toRemove); toast.success(t("toast.updated")); setView("group-detail"); } else { await createGroup(jmapClient, name, memberIds); toast.success(t("toast.created")); setView("list"); } }, [view, selectedGroup, selectedGroupMembers, supportsSync, client, createGroup, updateGroup, addMembersToGroup, removeMembersFromGroup, t]); const handleRemoveGroupMember = async (memberId: string) => { if (!selectedGroup) return; try { await removeMembersFromGroup( supportsSync && client ? client : null, selectedGroup.id, [memberId] ); toast.success(t("toast.updated")); } catch (error) { console.error('Failed to remove group member:', error); toast.error(t("toast.error_update")); } }; const handleBulkDelete = async () => { if (selectedContactIds.size === 0) return; const confirmed = await confirmDialog({ title: t("bulk.delete_confirm_title"), message: t("bulk.delete_confirm", { count: selectedContactIds.size }), confirmText: t("bulk.delete"), variant: "destructive", }); if (!confirmed) return; try { await bulkDeleteContacts( supportsSync && client ? client : null, Array.from(selectedContactIds) ); toast.success(t("bulk.deleted", { count: selectedContactIds.size })); setView("list"); } catch (error) { console.error('Failed to bulk delete contacts:', error); toast.error(t("toast.error_delete")); } }; const handleBulkAddToGroup = () => { if (selectedContactIds.size === 0) return; if (groups.length === 0) { setView("group-create"); return; } setView("bulk-add-to-group"); }; const handleBulkExport = () => { const toExport = contacts.filter(c => selectedContactIds.has(c.id)); if (toExport.length > 0) { exportContacts(toExport); toast.success(t("export.success", { count: toExport.length })); clearSelection(); } }; const handleBulkAddToGroupConfirm = async (groupId: string) => { try { await bulkAddToGroup( supportsSync && client ? client : null, groupId, Array.from(selectedContactIds) ); toast.success(t("bulk.added_to_group")); setView("list"); } catch (error) { console.error('Failed to add contacts to group:', error); toast.error(t("toast.error_update")); } }; const handleImport = useCallback(async (importedContacts: ContactCard[]) => { return importContacts( supportsSync && client ? client : null, importedContacts ); }, [supportsSync, client, importContacts]); if (!isAuthenticated) return null; const renderRightPanel = () => { switch (view) { case "create": return ; case "edit": if (!selectedContact) return null; return ( ); case "group-detail": if (!selectedGroup) return null; return ( { setSelectedContact(id); setActiveTab("all"); setView("detail"); }} /> ); case "group-create": return ( ); case "group-edit": if (!selectedGroup) return null; return ( m.id)} onSave={handleSaveGroup} onCancel={handleCancel} /> ); case "import": return ( ); case "bulk-add-to-group": return (

{t("bulk.choose_group")}

{t("bulk.adding_contacts", { count: selectedContactIds.size })}

{groups.map((group) => { const gName = getContactDisplayName(group); const memberCount = group.members ? Object.values(group.members).filter(Boolean).length : 0; return ( ); })}
); default: return ( ); } }; const showListPanel = !isMobile || view === "list"; const showRightPanel = !isMobile || view !== "list"; const mobileBackToList = () => { setView("list"); clearSelection(); }; return (
{!isMobile && (
{ logout(); router.push('/login'); }} />
)}
{showListPanel && (
{activeTab === "all" ? ( setView("import")} supportsSync={supportsSync} className="flex-1" selectedContactIds={selectedContactIds} onToggleSelection={toggleContactSelection} onSelectAll={selectAllContacts} onClearSelection={clearSelection} onBulkDelete={handleBulkDelete} onBulkAddToGroup={handleBulkAddToGroup} onBulkExport={handleBulkExport} /> ) : ( )}
)} {showRightPanel && (
{isMobile && (
)}
{renderRightPanel()}
)}
{isMobile && ( )}
); }