mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
Switching is three acts in a fixed order, and the order is the safety argument. Ask the server, so a refused switch leaves the browser where it was. Empty the caches a reload cannot reach, awaited rather than fired, because a wipe racing the navigation is a wipe that sometimes does not happen. Then reload the document, which is what discards the in-memory cache, the rendered React trees, and every request already in flight against the old record. The banner is the feature, not decoration. Someone who forgets they are switched will log their own blood pressure into another person's record, and there is no undo for a reading that was never theirs. So it states whose record is open, that it is read-only, and carries the way out — in the one visual register reserved for "something about this screen is not normal", at the top of the shell above every other strip. A subtle tint is what people stop seeing on the third switch. The switcher lives in one component mounted in both user menus. A way in on the laptop and no way out on the phone is how the two menus have drifted before. Nav entries that sharing does not cover drop out, and a page reached by bookmark says so instead of painting a row of refusals. Both are paint: the server refuses those routes on its own, from a frozen list this code cannot reach. Settings, connected services, notifications and the AI surfaces are the account around a record rather than the record itself. A grant can end while a browser is inside the record. Revocation clears the delegate's sessions in the same transaction; expiry does not, and leaves a session stamped with an account it may no longer act on — correct refusals under a screen that lies. One cache subscriber notices the stable errorCode and leaves, rather than asking two hundred call sites to remember.
53 lines
1.9 KiB
TypeScript
53 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { Lock } from "lucide-react";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { EmptyState } from "@/components/ui/empty-state";
|
|
import { useAccountSwitch } from "@/hooks/use-account-switch";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
|
|
/**
|
|
* v1.36.0 — the page a delegate reached that sharing does not cover.
|
|
*
|
|
* The nav drops these destinations while a switch is on, so the way here is a
|
|
* bookmark, a browser back button, or a link somebody kept open. Without this
|
|
* panel the page would mount, fire its reads, collect a row of 403s, and paint
|
|
* a screen full of error cards that each say something went wrong — when
|
|
* nothing did.
|
|
*
|
|
* It says the honest thing instead: this surface is not part of what sharing
|
|
* covers, and here is the way back to your own record. Settings, credentials,
|
|
* integrations, notification channels and the AI surfaces are the account
|
|
* around a health record rather than the record itself; a delegate reads the
|
|
* record.
|
|
*
|
|
* Paint, not enforcement. Every route behind these pages refuses on its own,
|
|
* from a frozen allowlist this component cannot reach — if this file were
|
|
* deleted the delegate would see 403s, not data.
|
|
*/
|
|
export function SharedRecordUnavailable() {
|
|
const { t } = useTranslations();
|
|
const switchAccount = useAccountSwitch();
|
|
|
|
return (
|
|
<div data-slot="shared-record-unavailable" className="py-10">
|
|
<EmptyState
|
|
icon={<Lock className="size-6" />}
|
|
title={t("recordSharing.unavailable.title")}
|
|
description={t("recordSharing.unavailable.description")}
|
|
ctaSize="lg"
|
|
action={
|
|
<Button
|
|
data-slot="shared-record-unavailable-leave"
|
|
disabled={switchAccount.isPending}
|
|
onClick={() => switchAccount.mutate(null)}
|
|
>
|
|
{t("recordSharing.unavailable.leave")}
|
|
</Button>
|
|
}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|