Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | 3x 5x 5x 5x 2x 1x 2x 2x 2x 2x 2x 2x 5x 2x 4x | "use client";
/**
* RegisterDialog — a lightweight modal wrapping EudiApprovalFlow. Used by the
* homepage "Register with EUDI Wallet" CTA and the /patient "Request EHR data"
* button. No dialog dependency — plain Tailwind overlay with ESC/backdrop close,
* role="dialog" + aria-modal, and body-scroll lock. z-[70] sits above the nav
* (z-50) and UserMenu dropdown.
*/
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { X, ShieldCheck, ScanLine } from "lucide-react";
import {
EudiApprovalFlow,
type ApprovalMode,
} from "@/components/wallet/EudiApprovalFlow";
/** Eyebrow label per approval mode, so the dialog's intent is unmistakable. */
const EYEBROW: Record<ApprovalMode, string> = {
register: "European Health Dataspace · Registration",
login: "European Health Dataspace · Sign in",
ehr: "Electronic Patient Record · ePA transfer",
};
export function RegisterDialog({
mode = "register",
title,
subtitle,
onClose,
onComplete,
}: {
mode?: ApprovalMode;
title: string;
subtitle?: string;
onClose: () => void;
onComplete: () => void;
}) {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
Eif (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
window.removeEventListener("keydown", onKey);
document.body.style.overflow = prev;
};
}, [onClose]);
if (!mounted) return null;
// Portal to <body> so the modal escapes the page's stacking context and sits
// above the sticky nav (z-50) — true "always on top".
return createPortal(
<div
role="dialog"
aria-modal="true"
aria-label={title}
className="fixed inset-0 z-[100] flex items-center justify-center p-4"
onClick={onClose}
>
<div className="absolute inset-0 bg-black/75 backdrop-blur-md" />
<div
className="relative z-10 w-full max-w-3xl rounded-2xl border-2 border-[var(--border)] bg-[var(--surface)] ring-1 ring-black/10 shadow-[0_24px_70px_rgba(0,0,0,0.45)] p-6 sm:p-8 max-h-[92vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<button
type="button"
onClick={onClose}
aria-label="Close"
className="absolute top-3 right-3 grid place-items-center w-9 h-9 rounded-full text-[var(--text-secondary)] hover:bg-[var(--surface-2)] transition-colors z-10"
>
<X size={18} />
</button>
{/* Branded header — makes the registration intent unmistakable */}
<div className="flex flex-col items-center text-center">
<span
className="inline-flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-[0.12em] px-3 py-1 rounded-full mb-3"
style={{
background: "rgba(36,113,163,0.12)",
color: "var(--accent)",
}}
>
<ShieldCheck size={13} /> {EYEBROW[mode]}
</span>
<h2 className="text-2xl font-extrabold text-[var(--text-primary)] leading-tight">
{title}
</h2>
{subtitle && (
<p className="text-sm text-[var(--text-secondary)] mt-1.5 max-w-md mx-auto">
{subtitle}
</p>
)}
<span className="inline-flex items-center gap-1.5 mt-3 text-xs font-semibold text-[var(--text-secondary)]">
<ScanLine size={13} /> Scan the QR or approve on your phone — no
password
</span>
</div>
<div className="mt-5">
<EudiApprovalFlow
mode={mode}
onComplete={onComplete}
onCancel={onClose}
/>
</div>
</div>
</div>,
document.body,
);
}
|