feat(sharing): add an error boundary to the public share route

A render failure on `/c/<token>` previously fell through to the full-app
error screen, which carries app chrome the public share view never wants.
Add a route-scoped, chrome-free error boundary that shows a localised,
stack-free message and a single retry. Add the matching copy across all
six locales.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 11:53:52 +02:00
1 parent be37a87fa7
commit ee2e9a3563
7 files changed
+71

No files matched your search

+5
View File
@@ -6358,6 +6358,11 @@
"label": "Passphrase",
"error": "Diese Passphrase ist nicht korrekt.",
"submit": "Entsperren"
},
"loadError": {
"title": "Diese geteilte Ansicht konnte nicht geladen werden",
"description": "Beim Öffnen dieser geteilten Gesundheitsdaten ist ein Fehler aufgetreten. Der Link ist möglicherweise abgelaufen, oder es lag ein vorübergehendes Problem vor. Versuche es erneut oder bitte die Person, die den Link geteilt hat, um einen neuen.",
"retry": "Erneut versuchen"
}
},
"cycle": {
+5
View File
@@ -6358,6 +6358,11 @@
"label": "Passphrase",
"error": "That passphrase is not correct.",
"submit": "Unlock"
},
"loadError": {
"title": "This shared view could not be loaded",
"description": "Something went wrong while opening this shared health record. The link may have expired, or there was a temporary problem. Try again, or ask the person who shared it for a new link.",
"retry": "Try again"
}
},
"cycle": {
+5
View File
@@ -6358,6 +6358,11 @@
"label": "Frase de acceso",
"error": "Esa frase de acceso no es correcta.",
"submit": "Desbloquear"
},
"loadError": {
"title": "No se pudo cargar esta vista compartida",
"description": "Se produjo un error al abrir este registro de salud compartido. Puede que el enlace haya caducado o que haya habido un problema temporal. Inténtalo de nuevo o pide un enlace nuevo a la persona que lo compartió.",
"retry": "Intentar de nuevo"
}
},
"cycle": {
+5
View File
@@ -6358,6 +6358,11 @@
"label": "Phrase secrète",
"error": "Cette phrase secrète est incorrecte.",
"submit": "Déverrouiller"
},
"loadError": {
"title": "Impossible de charger cette vue partagée",
"description": "Une erreur s'est produite lors de l'ouverture de ce dossier de santé partagé. Le lien a peut-être expiré, ou un problème temporaire est survenu. Réessayez ou demandez un nouveau lien à la personne qui l'a partagé.",
"retry": "Réessayer"
}
},
"cycle": {
+5
View File
@@ -6358,6 +6358,11 @@
"label": "Passphrase",
"error": "La passphrase non è corretta.",
"submit": "Sblocca"
},
"loadError": {
"title": "Impossibile caricare questa vista condivisa",
"description": "Si è verificato un errore durante l'apertura di questo referto sanitario condiviso. Il link potrebbe essere scaduto oppure si è verificato un problema temporaneo. Riprova o chiedi un nuovo link alla persona che lo ha condiviso.",
"retry": "Riprova"
}
},
"cycle": {
+5
View File
@@ -6358,6 +6358,11 @@
"label": "Hasło",
"error": "To hasło jest nieprawidłowe.",
"submit": "Odblokuj"
},
"loadError": {
"title": "Nie udało się załadować udostępnionego widoku",
"description": "Podczas otwierania udostępnionej dokumentacji zdrowotnej wystąpił błąd. Link mógł wygasnąć lub wystąpił chwilowy problem. Spróbuj ponownie albo poproś osobę, która go udostępniła, o nowy link.",
"retry": "Spróbuj ponownie"
}
},
"cycle": {
+41
View File
@@ -0,0 +1,41 @@
"use client";
/**
* Error boundary for the public clinician share route (`/c/<token>`).
*
* The share view carries no app chrome and no session, so a render failure must
* not fall through to the full-app error screen. This renders a clean, locale-
* aware, stack-free message and offers a single retry. No record data, no
* digest, no stack — a probe learns nothing from a failed render.
*/
import { useTranslations } from "@/lib/i18n/context";
import { Button } from "@/components/ui/button";
export default function ClinicianShareError({
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
const { t } = useTranslations();
return (
<main className="mx-auto flex min-h-screen max-w-md flex-col justify-center px-4">
<div className="border-border bg-card space-y-4 rounded-lg border p-6 text-center">
<h1 className="text-lg font-semibold">
{t("clinicianView.loadError.title")}
</h1>
<p className="text-muted-foreground text-sm">
{t("clinicianView.loadError.description")}
</p>
<Button
onClick={() => reset()}
className="min-h-11 w-full sm:min-h-9"
variant="outline"
>
{t("clinicianView.loadError.retry")}
</Button>
</div>
</main>
);
}