All files / src/app/demo page.tsx

0% Statements 0/19
0% Branches 0/11
0% Functions 0/8
0% Lines 0/18

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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167                                                                                                                                                                                                                                                                                                                                             
"use client";
 
/**
 * Demo Persona Hub — /demo
 *
 * Static GitHub Pages entry point. Click a persona card to:
 *   1. Store the username in localStorage (demo-persona key)
 *   2. Navigate to that persona's primary feature page
 *
 * From that point on, Navigation and UserMenu read the stored persona and
 * render the correct role-filtered view without any Keycloak session.
 */
 
import { useRouter } from "next/navigation";
import {
  Shield,
  User,
  BarChart2,
  ShieldCheck,
  Network,
  Heart,
  Users,
  ArrowRight,
} from "lucide-react";
import { DEMO_PERSONAS, ROLE_LABELS } from "@/lib/auth";
import { setDemoPersona } from "@/lib/use-demo-persona";
 
/** Where each persona lands after selection */
const PERSONA_HOME: Record<string, string> = {
  "edc-admin": "/graph?persona=edc-admin",
  hospital: "/catalog",
  researcher: "/analytics",
  hdab: "/compliance",
  patient: "/patient/profile",
  default: "/graph",
};
 
/** Icon per persona */
const PERSONA_ICON: Record<string, React.ReactNode> = {
  "edc-admin": <Network size={24} />,
  hospital: <Shield size={24} />,
  researcher: <BarChart2 size={24} />,
  hdab: <ShieldCheck size={24} />,
  patient: <Heart size={24} />,
  default: <User size={24} />,
};
 
export default function DemoHubPage() {
  const router = useRouter();
 
  function handleSelect(username: string, personaId: string) {
    setDemoPersona(username);
    router.push(PERSONA_HOME[personaId] ?? "/graph");
  }
 
  // Deduplicate by personaId to group similar roles (clinicuser + lmcuser both map to hospital)
  const seen = new Set<string>();
  const uniquePersonas = DEMO_PERSONAS.filter((p) => {
    const key = `${p.username}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
 
  return (
    <div className="max-w-4xl mx-auto px-6 py-14">
      {/* Header */}
      <div className="flex items-center gap-3 mb-2">
        <Users size={28} className="text-blue-800 dark:text-blue-300" />
        <h1 className="text-3xl font-bold">Demo Persona Selector</h1>
      </div>
      <p className="text-[var(--text-secondary)] mb-2">
        Explore the Health Dataspace as any of the 7 demo participants — no
        login required.
      </p>
      <p className="text-sm text-[var(--text-secondary)] mb-10">
        Select a persona below. Navigation, data, and views will update to match
        that participant&apos;s role. Switch anytime via the user menu{" "}
        <span className="text-[var(--text-secondary)]">→</span>{" "}
        <em>Switch demo persona</em>.
      </p>
 
      {/* Persona cards */}
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
        {uniquePersonas.map((persona) => {
          const primaryRole = [...persona.roles].find((r) =>
            [
              "EDC_ADMIN",
              "HDAB_AUTHORITY",
              "DATA_HOLDER",
              "DATA_USER",
              "PATIENT",
            ].includes(r),
          );
 
          return (
            <button
              key={persona.username}
              onClick={() => handleSelect(persona.username, persona.personaId)}
              className={`group text-left rounded-2xl border p-6 transition-all hover:scale-[1.02] hover:shadow-lg ${persona.badge} bg-[var(--surface)] hover:bg-[var(--surface-2)]`}
            >
              {/* Icon + username */}
              <div className={`flex items-center gap-3 mb-3 ${persona.color}`}>
                {PERSONA_ICON[persona.personaId] ?? <User size={24} />}
                <span className="font-mono font-semibold text-lg">
                  {persona.username}
                </span>
              </div>
 
              {/* Organisation */}
              <p className="text-sm text-[var(--text-primary)] font-medium mb-1">
                {persona.organisation}
              </p>
 
              {/* Description */}
              <p className="text-xs text-[var(--text-secondary)] mb-4 leading-relaxed">
                {persona.description}
              </p>
 
              {/* Role badges */}
              <div className="flex flex-wrap gap-1.5 mb-4">
                {([...persona.roles] as string[])
                  .filter((r) => r !== "EDC_USER_PARTICIPANT")
                  .map((r) => (
                    <span
                      key={r}
                      className={`inline-flex items-center gap-1 text-[10px] px-2 py-0.5 rounded-full font-medium border ${persona.badge}`}
                    >
                      <Shield size={8} />
                      {ROLE_LABELS[r] ?? r}
                    </span>
                  ))}
                {primaryRole === undefined &&
                  ([...persona.roles] as string[]).includes(
                    "EDC_USER_PARTICIPANT",
                  ) && (
                    <span
                      className={`inline-flex items-center gap-1 text-[10px] px-2 py-0.5 rounded-full font-medium border ${persona.badge}`}
                    >
                      <Shield size={8} />
                      Participant
                    </span>
                  )}
              </div>
 
              {/* CTA */}
              <div
                className={`flex items-center gap-1 text-xs font-semibold ${persona.color} group-hover:gap-2 transition-all`}
              >
                Start demo
                <ArrowRight size={13} />
              </div>
            </button>
          );
        })}
      </div>
 
      {/* Footer note */}
      <p className="mt-12 text-xs text-[var(--text-secondary)] text-center">
        Static demo — all data is synthetic. No login, no real patient records.
        <br />
        EHDS Art. 3 · GDPR Art. 15-22 · DSP 2025-1 · FHIR R4 · OMOP CDM
      </p>
    </div>
  );
}