All files / src/components RegisterDialog.tsx

100% Statements 18/18
85.71% Branches 6/7
100% Functions 6/6
100% Lines 15/15

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,
  );
}