frontend: relay-based Patient Portal QR + kiosk "Link wallet" option

- lib/portal.ts: getPortalLink + portalPairingUri build a temetro-portal: URI
  (relay URL + clinic signing key) instead of a localhost API URL.
- Signing settings QR now encodes that pairing URI, so a real phone can reach
  the clinic over the Temetro Network relay (fixes "server cannot be accessed").
- Portal kiosk gains a third "Link my wallet" option that shows the same QR.
- New portal.choose.wallet* / portal.wallet.* keys in all 5 locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-07-09 00:06:04 +03:00
parent dd64d689c8
commit 947623a691
8 changed files with 151 additions and 16 deletions
@@ -2172,7 +2172,15 @@
"bookTitle": "حجز موعد",
"bookDesc": "جدول زيارة مع فريق الرعاية الخاص بك.",
"resultsTitle": "عرض نتائجي",
"resultsDesc": "تحقّق من الزيارات القادمة وما إذا كانت النتائج جاهزة."
"resultsDesc": "تحقّق من الزيارات القادمة وما إذا كانت النتائج جاهزة.",
"walletTitle": "ربط محفظتي",
"walletDesc": "استخدم تطبيق temetro لحجز المواعيد وعرض النتائج بنفسك."
},
"wallet": {
"title": "اربط محفظتك",
"subtitle": "امسح هذا الرمز بتطبيق محفظة temetro لربطه بهذه العيادة.",
"error": "تعذّر تحميل رمز الربط. الرجاء سؤال مكتب الاستقبال.",
"hint": "بعد الربط، يمكنك حجز المواعيد وعرض النتائج من هاتفك."
},
"field": {
"name": "الاسم الكامل",
@@ -2152,7 +2152,15 @@
"bookTitle": "Einen Termin buchen",
"bookDesc": "Vereinbaren Sie einen Besuch bei Ihrem Behandlungsteam.",
"resultsTitle": "Meine Ergebnisse ansehen",
"resultsDesc": "Prüfen Sie bevorstehende Besuche und ob Ergebnisse bereit sind."
"resultsDesc": "Prüfen Sie bevorstehende Besuche und ob Ergebnisse bereit sind.",
"walletTitle": "Wallet verknüpfen",
"walletDesc": "Nutzen Sie die temetro-App, um selbst zu buchen und Ergebnisse zu sehen."
},
"wallet": {
"title": "Wallet verknüpfen",
"subtitle": "Scannen Sie diesen Code mit der temetro-Wallet-App, um sie mit dieser Klinik zu verbinden.",
"error": "Der Kopplungscode konnte nicht geladen werden. Bitte fragen Sie an der Rezeption.",
"hint": "Nach der Verknüpfung können Sie Termine buchen und Ergebnisse auf Ihrem Telefon ansehen."
},
"field": {
"name": "Vollständiger Name",
@@ -2152,7 +2152,15 @@
"bookTitle": "Book an appointment",
"bookDesc": "Schedule a visit with your care team.",
"resultsTitle": "View my results",
"resultsDesc": "Check upcoming visits and whether results are ready."
"resultsDesc": "Check upcoming visits and whether results are ready.",
"walletTitle": "Link my wallet",
"walletDesc": "Use the temetro app to book and view results yourself."
},
"wallet": {
"title": "Link your wallet",
"subtitle": "Scan this code with the temetro wallet app to connect it to this clinic.",
"error": "Couldn't load the pairing code. Please ask the front desk.",
"hint": "After linking, you can book appointments and view results from your phone."
},
"field": {
"name": "Full name",
@@ -2152,7 +2152,15 @@
"bookTitle": "Prendre un rendez-vous",
"bookDesc": "Planifiez une visite avec votre équipe soignante.",
"resultsTitle": "Voir mes résultats",
"resultsDesc": "Consultez les prochaines visites et si les résultats sont prêts."
"resultsDesc": "Consultez les prochaines visites et si les résultats sont prêts.",
"walletTitle": "Lier mon portefeuille",
"walletDesc": "Utilisez l'application temetro pour réserver et voir vos résultats vous-même."
},
"wallet": {
"title": "Lier votre portefeuille",
"subtitle": "Scannez ce code avec l'application portefeuille temetro pour la connecter à cette clinique.",
"error": "Impossible de charger le code d'association. Veuillez demander à l'accueil.",
"hint": "Une fois lié, vous pourrez prendre rendez-vous et consulter vos résultats depuis votre téléphone."
},
"field": {
"name": "Nom complet",
@@ -2152,7 +2152,15 @@
"bookTitle": "Ballan qabso",
"bookDesc": "Qorshee booqasho kooxdaada daryeelka.",
"resultsTitle": "Eeg natiijooyinkayga",
"resultsDesc": "Hubi booqashooyinka soo socda iyo haddii natiijooyinku diyaar yihiin."
"resultsDesc": "Hubi booqashooyinka soo socda iyo haddii natiijooyinku diyaar yihiin.",
"walletTitle": " Isku xir walletkayga",
"walletDesc": "Isticmaal abka temetro si aad adigu u ballansato oo aad u aragto natiijooyinka."
},
"wallet": {
"title": "Isku xir walletkaaga",
"subtitle": "Ku sawir koodhkan abka wallet-ka temetro si aad ugu xidho rugtan caafimaad.",
"error": "Lama soo rari karin koodhka isku xirka. Fadlan weydii miiska hore.",
"hint": "Kadib xirka, waxaad ballan ka samaysan kartaa oo natiijooyinka ka arki kartaa taleefankaaga."
},
"field": {
"name": "Magaca oo dhan",
+24
View File
@@ -65,6 +65,30 @@ export function getPortalClinic(clinic: string): Promise<PortalClinic> {
return portalFetch<PortalClinic>(`/${encodeURIComponent(clinic)}`);
}
// The relay-based pairing descriptor the wallet app scans: the clinic's signing
// public key (relay routing id) + the relay URL. Non-secret values.
export type PortalLink = {
clinicId: string;
relay: string;
slug: string;
name: string;
};
export function getPortalLink(clinic: string): Promise<PortalLink> {
return portalFetch<PortalLink>(`/${encodeURIComponent(clinic)}/link`);
}
// Build the `temetro-portal:` URI the wallet app scans to reach this clinic over
// the Temetro Network relay (no localhost API URL — works from a real phone).
export function portalPairingUri(link: PortalLink): string {
const params = new URLSearchParams({
relay: link.relay,
clinic: link.clinicId,
slug: link.slug,
});
return `temetro-portal:?${params.toString()}`;
}
export type PortalNewPatient = {
name: string;
sex?: string;