mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
fix(sharing): show a guardian the controls the routes accept (#939)
useRecordCapabilities answers per section now. canWriteDomain and canManageDomain read the two lists the account payload publishes; in one's own record they are always true, and under a refused, pending or unproven context always false. canManage becomes the coarse switch, true when any section is manageable, and is what the banner and the navigation read. Every add, edit and delete control names its own section: labs, mind, profile, illness, cycle, medications, measurements, documents. Creates that a WRITE route answers (visits, practitioners) ask canWriteDomain. The generic delete button, the selection bar, the capture picker, the dashboard quick entry and the bottom nav take the section from their callers. Controls whose routes resolve the caller rather than the record (settings shortcuts, chart overlay preferences, custom metrics, the Today rail, OCR, efficacy targets, credential tabs) ask for the caller's own record instead, and the customise-shortcut guard now requires that shape. The vault has no delegated write route, so its controls stay withheld under every grant, and the documents page says so in one sentence where the upload control would be, in all seven locales.
This commit is contained in:
1 parent
b5c392636f
commit
e95612e3d3
64 files changed
+690
-289
No files matched your search
@@ -16,6 +16,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
|
||||
import type { ShareDomain } from "@/lib/sharing/scope";
|
||||
|
||||
/**
|
||||
* Shared single-row delete button + confirm dialog for the data-
|
||||
@@ -30,11 +31,14 @@ import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
|
||||
* v1.36.x — this is the one place a row delete is spelled across the app
|
||||
* (measurements, lab results, biomarkers, allergies, family history, custom
|
||||
* metrics, medication inventory), so the delegation rule is enforced here
|
||||
* rather than at twelve call sites: deleting is the owner's alone, at every
|
||||
* grant level, so inside somebody else's record the trigger is absent. Not
|
||||
* disabled — a greyed bin still claims the row is the delegate's to remove.
|
||||
* A caller that ever needs a delete a delegate MAY perform would have to say
|
||||
* so explicitly, and today there is none.
|
||||
* rather than at twelve call sites. Not disabled when withheld — a greyed bin
|
||||
* still claims the row is the delegate's to remove.
|
||||
*
|
||||
* v1.38.12 — deleting is a MANAGE verb, and whether a MANAGE grant reaches it
|
||||
* depends on the section the row belongs to (`domain-write-support.ts`), so
|
||||
* the caller names the section and this component asks `canManageDomain` for
|
||||
* it. `null` says the row's delete route resolves the caller rather than the
|
||||
* record (custom metrics), which no grant reaches at any level.
|
||||
*/
|
||||
export function DeleteButton({
|
||||
onConfirm,
|
||||
@@ -45,7 +49,10 @@ export function DeleteButton({
|
||||
triggerTitle,
|
||||
className = "",
|
||||
iconClassName = "h-3.5 w-3.5",
|
||||
domain,
|
||||
}: {
|
||||
/** The section the delete route answers under, or `null` for owner-only. */
|
||||
domain: ShareDomain | null;
|
||||
onConfirm: () => void;
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -57,8 +64,9 @@ export function DeleteButton({
|
||||
iconClassName?: string;
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
const { canManage } = useRecordCapabilities();
|
||||
if (!canManage) return null;
|
||||
const { canManageDomain, inSharedRecord } = useRecordCapabilities();
|
||||
const allowed = domain === null ? !inSharedRecord : canManageDomain(domain);
|
||||
if (!allowed) return null;
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
|
||||
import type { ShareDomain } from "@/lib/sharing/scope";
|
||||
|
||||
/**
|
||||
* Selection action bar for the data-management lists (measurements +
|
||||
@@ -31,10 +32,13 @@ import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
|
||||
* The confirm copy is passed in because the title/body differ per surface
|
||||
* (measurements vs mood) and the count is interpolated by the caller.
|
||||
*
|
||||
* v1.36.x — bulk delete is the owner's alone at every grant level, so the bar
|
||||
* is absent inside somebody else's record. The row checkboxes that feed it are
|
||||
* dropped at their lists for the same reason; this guard is the backstop, so a
|
||||
* list that forgets cannot leave a delete button floating over the page.
|
||||
* v1.36.x — bulk delete is a MANAGE verb, so the bar is absent inside somebody
|
||||
* else's record below that level. The row checkboxes that feed it are dropped
|
||||
* at their lists for the same reason; this guard is the backstop, so a list
|
||||
* that forgets cannot leave a delete button floating over the page.
|
||||
*
|
||||
* v1.38.12 — the caller names the section its bulk route answers under, and
|
||||
* the bar asks `canManageDomain` for it; `null` marks an owner-only route.
|
||||
*/
|
||||
export function SelectionActionBar({
|
||||
count,
|
||||
@@ -43,7 +47,10 @@ export function SelectionActionBar({
|
||||
isDeleting,
|
||||
confirmTitle,
|
||||
confirmBody,
|
||||
domain,
|
||||
}: {
|
||||
/** The section the bulk-delete route answers under, or `null` for owner-only. */
|
||||
domain: ShareDomain | null;
|
||||
count: number;
|
||||
onClear: () => void;
|
||||
onConfirmDelete: () => void;
|
||||
@@ -52,9 +59,10 @@ export function SelectionActionBar({
|
||||
confirmBody: string;
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
const { canManage } = useRecordCapabilities();
|
||||
const { canManageDomain, inSharedRecord } = useRecordCapabilities();
|
||||
const allowed = domain === null ? !inSharedRecord : canManageDomain(domain);
|
||||
|
||||
if (count <= 0 || !canManage) return null;
|
||||
if (count <= 0 || !allowed) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in new issue
Block a user