Let the owner stop sharing a calendar or an address book
Revoking a share meant opening the share dialog, removing each person from it in turn, and saving. That is the right tool for changing who has access and the wrong one for withdrawing it altogether, which is the more urgent of the two and the one someone is likely to want in a hurry. Both now offer "Stop sharing" in the context menu, which clears the lot after a confirmation saying how many people lose access. It appears only when there is something to revoke, so the menu says whether a thing is shared as well as offering to change it. A calendar also says it is shared now. Address books have carried that badge since they gained sharing; calendars never did, so the only way to find out was to open the dialog and look -- which for the owner of a dozen calendars means opening a dozen dialogs. Both go through the existing update paths, so a server that refuses is reported rather than swallowed. Verified against the mock, both kinds: sharing one shows the badge and adds the entry, confirming clears `shareWith`, the badge goes, and the entry disappears with it since there is no longer anything to stop.
This commit is contained in:
@@ -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() {
|
||||
<div key={c.id} className={`cal-list-item ${cal.hidden[c.id] ? "hidden-cal" : ""}`} onClick={() => cal.toggleHidden(c.id)} onContextMenu={(e) => { e.preventDefault(); setMenuCal(c); menu.openAt(e.clientX, e.clientY); }}>
|
||||
<span className="cal-color" style={{ background: c.color ?? "var(--accent)", borderColor: c.color ?? "var(--accent)" }} />
|
||||
<span className="cal-name">{c.name}</span>
|
||||
{Object.keys(c.shareWith ?? {}).length > 0 && <Share2 size={12} className="faint" aria-label="Shared" />}
|
||||
{c.isDefault && <Star size={12} className="faint" />}
|
||||
<button className="icon-btn xs nav-more" onClick={(e) => { e.stopPropagation(); setMenuCal(c); menu.open(e); }} aria-label="Calendar options"><MoreVertical size={14} /></button>
|
||||
</div>
|
||||
@@ -123,6 +124,31 @@ export function CalendarSidebar() {
|
||||
<MenuItem icon={cal.hidden[menuCal.id] ? <Eye size={16} /> : <EyeOff size={16} />} label={cal.hidden[menuCal.id] ? "Show" : "Hide"} onClick={() => cal.toggleHidden(menuCal.id)} />
|
||||
<MenuItem icon={<Pencil size={16} />} label="Edit" onClick={() => setEditCal(menuCal)} />
|
||||
<MenuItem icon={<Share2 size={16} />} 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 && (
|
||||
<MenuItem
|
||||
icon={<UserMinus size={16} />}
|
||||
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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MenuItem icon={<Star size={16} />} label="Make default" disabled={menuCal.isDefault} onClick={() => void cal.updateCalendar(menuCal.id, { isDefault: true } as Partial<Calendar>).catch((err) => toast.error((err as Error).message))} />
|
||||
<MenuSep />
|
||||
<MenuItem danger icon={<Trash2 size={16} />} 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)); }} />
|
||||
|
||||
@@ -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() {
|
||||
}}
|
||||
/>
|
||||
<MenuItem icon={<Share2 size={16} />} 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 && (
|
||||
<MenuItem
|
||||
icon={<UserMinus size={16} />}
|
||||
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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MenuSep />
|
||||
<MenuItem
|
||||
danger
|
||||
|
||||
Reference in New Issue
Block a user