feat: add "New address book" creation UI #415

This commit is contained in:
Linus Rath
2026-06-24 19:53:41 +02:00
parent f285a2bd64
commit de68d68fb7
24 changed files with 171 additions and 3 deletions
+12 -1
View File
@@ -2,7 +2,7 @@
import { useMemo, useState, useCallback, useEffect, useRef, type DragEvent } from "react";
import { useTranslations } from "next-intl";
import { BookUser, User, Users, Plus, Share2, Book, ChevronRight, ChevronDown, UserPlus, UsersRound, Upload, Tag, Pencil, Trash2, Settings, Mail } from "lucide-react";
import { BookUser, User, Users, Plus, Share2, Book, BookPlus, ChevronRight, ChevronDown, UserPlus, UsersRound, Upload, Tag, Pencil, Trash2, Settings, Mail } from "lucide-react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { ContextMenu, ContextMenuItem, ContextMenuSeparator, ContextMenuSubMenu } from "@/components/ui/context-menu";
@@ -22,6 +22,7 @@ interface ContactsSidebarProps {
onSelectCategory: (category: ContactCategory) => void;
onCreateGroup: () => void;
onCreateContact: () => void;
onCreateAddressBook?: () => void;
onImport?: () => void;
onEditGroup?: (groupId: string) => void;
onDeleteGroup?: (groupId: string) => void;
@@ -91,6 +92,7 @@ export function ContactsSidebar({
onSelectCategory,
onCreateGroup,
onCreateContact,
onCreateAddressBook,
onImport,
onEditGroup,
onDeleteGroup,
@@ -298,6 +300,15 @@ export function ContactsSidebar({
<UsersRound className="w-4 h-4" />
{t("groups.create")}
</button>
{onCreateAddressBook && (
<button
className="w-full flex items-center gap-2 px-3 py-1.5 text-sm hover:bg-accent transition-colors text-left"
onClick={() => { setShowMenu(false); onCreateAddressBook(); }}
>
<BookPlus className="w-4 h-4" />
{t("address_books.create")}
</button>
)}
{onImport && (
<button
className="w-full flex items-center gap-2 px-3 py-1.5 text-sm hover:bg-accent transition-colors text-left"
@@ -2,7 +2,7 @@
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import { Book, Pencil, Share2, Tag, Users } from "lucide-react";
import { Book, BookPlus, Pencil, Share2, Tag, Users } from "lucide-react";
import { useContactStore } from "@/stores/contact-store";
import { useAuthStore } from "@/stores/auth-store";
import { useManagedAccountStore } from "@/stores/managed-account-store";
@@ -11,6 +11,7 @@ import { SettingsSection } from "./settings-section";
import { cn } from "@/lib/utils";
import type { AddressBook, AddressBookRights } from "@/lib/jmap/types";
import { ShareCollectionDialog } from "./share-collection-dialog";
import { RenameDialog } from "@/components/files/rename-dialog";
function AddressBookEditRow({
initial,
@@ -73,10 +74,11 @@ export function AddressBookManagementSettings() {
const tSettings = useTranslations("settings.contacts");
const { client } = useAuthStore();
const managedAccountId = useManagedAccountStore((s) => s.managedAccountId);
const { addressBooks, contacts, supportsSync, fetchAddressBooks, renameAddressBook, shareAddressBook, renameKeyword } = useContactStore();
const { addressBooks, contacts, supportsSync, fetchAddressBooks, createAddressBook, renameAddressBook, shareAddressBook, renameKeyword } = useContactStore();
const [editingId, setEditingId] = useState<string | null>(null);
const [editingKeyword, setEditingKeyword] = useState<string | null>(null);
const [sharingId, setSharingId] = useState<string | null>(null);
const [creating, setCreating] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
@@ -85,6 +87,21 @@ export function AddressBookManagementSettings() {
}
}, [client, addressBooks.length, fetchAddressBooks]);
const handleCreate = async (name: string) => {
if (!client) return;
setIsLoading(true);
try {
await createAddressBook(client, name);
await fetchAddressBooks(client);
setCreating(false);
toast.success(t("created"));
} catch {
toast.error(t("create_failed"));
} finally {
setIsLoading(false);
}
};
const handleUpdate = async (book: AddressBook, newName: string) => {
if (!client) return;
setIsLoading(true);
@@ -226,6 +243,19 @@ export function AddressBookManagementSettings() {
{addressBooks.length === 0 && (
<p className="text-sm text-muted-foreground py-2">{tSettings("no_address_books")}</p>
)}
{/* Creating targets the user's own account, so hide it while scoped to a
managed (shared) account. */}
{!managedAccountId && client && (
<button
type="button"
onClick={() => setCreating(true)}
className="flex items-center gap-2 py-2.5 px-3 w-full rounded-md border border-dashed border-border text-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
>
<BookPlus className="w-4 h-4 flex-shrink-0" />
{t("create")}
</button>
)}
</div>
</SettingsSection>
@@ -278,6 +308,16 @@ export function AddressBookManagementSettings() {
</div>
)}
{creating && (
<RenameDialog
currentName=""
title={t("create")}
label={t("name_label")}
onCancel={() => setCreating(false)}
onConfirm={handleCreate}
/>
)}
{sharingId && client && (() => {
const book = addressBooks.find((b) => b.id === sharingId);
if (!book) return null;