diff --git a/web/src/views/calendar/CalendarSidebar.tsx b/web/src/views/calendar/CalendarSidebar.tsx index 2cd8b10..8abbb50 100644 --- a/web/src/views/calendar/CalendarSidebar.tsx +++ b/web/src/views/calendar/CalendarSidebar.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; import { useLocation } from "wouter"; -import { ChevronLeft, ChevronRight, MoreVertical, Pencil, Plus, Share2, Trash2, Eye, EyeOff, Star, X } from "lucide-react"; +import { ChevronLeft, ChevronRight, MoreVertical, Pencil, Plus, Share2, Trash2, Eye, EyeOff, Star, UserMinus, X } from "lucide-react"; import { useCalendar } from "@/store/calendar"; import { dateTimeKey, useSettings } from "@/store/settings"; import { addMonths, isSameDay, isToday, monthGrid, startOfDay, toLocalDateOnly } from "@/lib/dates"; @@ -66,6 +66,7 @@ export function CalendarSidebar() {
cal.toggleHidden(c.id)} onContextMenu={(e) => { e.preventDefault(); setMenuCal(c); menu.openAt(e.clientX, e.clientY); }}> {c.name} + {Object.keys(c.shareWith ?? {}).length > 0 && } {c.isDefault && }
@@ -123,6 +124,31 @@ export function CalendarSidebar() { : } label={cal.hidden[menuCal.id] ? "Show" : "Hide"} onClick={() => cal.toggleHidden(menuCal.id)} /> } label="Edit" onClick={() => setEditCal(menuCal)} /> } label="Share…" onClick={() => setShare(menuCal)} disabled={!menuCal.myRights.mayShare} /> + {/* Revoking every share at once, without walking the dialog and + removing people one at a time. Only offered when there is + something to revoke. */} + {Object.keys(menuCal.shareWith ?? {}).length > 0 && ( + } + label="Stop sharing" + disabled={!menuCal.myRights.mayShare} + onClick={async () => { + const who = Object.keys(menuCal.shareWith ?? {}).length; + if (!(await confirmDialog({ + title: `Stop sharing “${menuCal.name}”?`, + message: `${who === 1 ? "One person" : `${who} people`} will lose access. Events in it are not affected.`, + confirmLabel: "Stop sharing", + danger: true, + }))) return; + try { + await cal.updateCalendar(menuCal.id, { shareWith: null }); + toast.success("No longer shared"); + } catch (err) { + toast.error((err as Error).message); + } + }} + /> + )} } label="Make default" disabled={menuCal.isDefault} onClick={() => void cal.updateCalendar(menuCal.id, { isDefault: true } as Partial).catch((err) => toast.error((err as Error).message))} /> } label="Delete" disabled={!menuCal.myRights.mayDelete} onClick={async () => { if (await confirmDialog({ title: `Delete “${menuCal.name}”?`, message: "All events in this calendar will be deleted.", confirmLabel: "Delete", danger: true })) void cal.destroyCalendar(menuCal.id).catch((err) => toast.error((err as Error).message)); }} /> diff --git a/web/src/views/contacts/ContactsSidebar.tsx b/web/src/views/contacts/ContactsSidebar.tsx index 065f5b4..17c2604 100644 --- a/web/src/views/contacts/ContactsSidebar.tsx +++ b/web/src/views/contacts/ContactsSidebar.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Book, BookOpen, Download, Pencil, Plus, RefreshCw, Share2, Trash2, Upload, Users, X } from "lucide-react"; +import { Book, BookOpen, Download, Pencil, Plus, RefreshCw, Share2, Trash2, Upload, UserMinus, Users, X } from "lucide-react"; import { useContacts } from "@/store/contacts"; import { useSession } from "@/store/session"; import { useSettings } from "@/store/settings"; @@ -192,6 +192,30 @@ export function ContactsSidebar() { }} /> } label="Share…" disabled={!menuBook.myRights?.mayShare} onClick={() => setShare(menuBook)} /> + {/* Revoking the lot, rather than removing people one at a time in + the dialog. Only shown when there is something to revoke. */} + {Object.keys(menuBook.shareWith ?? {}).length > 0 && ( + } + label="Stop sharing" + disabled={!menuBook.myRights?.mayShare} + onClick={async () => { + const who = Object.keys(menuBook.shareWith ?? {}).length; + if (!(await confirmDialog({ + title: `Stop sharing “${menuBook.name}”?`, + message: `${who === 1 ? "One person" : `${who} people`} will lose access. The contacts in it are not affected.`, + confirmLabel: "Stop sharing", + danger: true, + }))) return; + try { + await contacts.updateBook(menuBook.id, { shareWith: null }); + toast.success("No longer shared"); + } catch (err) { + toast.error((err as Error).message); + } + }} + /> + )}