< Summary

Information
Class: tui-user-panel.tsx
Assembly: app.components
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/components/tui-user-panel.tsx
Tag: 58_25416222083
Line coverage
0%
Covered lines: 0
Uncovered lines: 31
Coverable lines: 31
Total lines: 102
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 23
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ClutterStock/ClutterStock/frontend/app/components/tui-user-panel.tsx

#LineLine coverage
 1import { useEffect, useRef } from "react";
 2import type { SessionUser } from "~/lib/session.server";
 3
 04export function TuiUserPanel({ user, open, onClose, onSignOut }: {
 5  user: SessionUser;
 6  open: boolean;
 7  onClose: () => void;
 8  onSignOut: () => void;
 9}) {
 010  const ref = useRef<HTMLDivElement>(null);
 11
 012  useEffect(() => {
 013    if (!open) return;
 014    function onDocPointer(e: MouseEvent) {
 015      if (!ref.current?.contains(e.target as Node)) onClose();
 16    }
 017    function onKey(e: KeyboardEvent) {
 018      if (e.key === "Escape") {
 019        e.preventDefault();
 020        onClose();
 021      } else if (e.key === "s") {
 22        // Only fire when focus is inside the panel (avoid hijacking 's' globally)
 023        if (ref.current?.contains(document.activeElement)) {
 024          e.preventDefault();
 025          onSignOut();
 26        }
 27      }
 28    }
 029    document.addEventListener("mousedown", onDocPointer);
 030    window.addEventListener("keydown", onKey);
 031    return () => {
 032      document.removeEventListener("mousedown", onDocPointer);
 033      window.removeEventListener("keydown", onKey);
 34    };
 35  }, [open, onClose, onSignOut]);
 36
 37  // Focus first action button when opened, so 's' / Esc work without an extra click
 038  useEffect(() => {
 039    if (open) ref.current?.querySelector<HTMLElement>("button")?.focus();
 40  }, [open]);
 41
 042  if (!open) return null;
 43
 044  const displayName = user.name ?? user.preferred_username ?? user.sub;
 045  const username    = user.preferred_username ?? "";
 046  const email       = user.email ?? "";
 047  const groups      = (user.groups ?? []).filter(Boolean);
 48
 049  return (
 50    <div
 51      ref={ref}
 52      role="dialog"
 53      aria-modal="false"
 54      aria-label="Account"
 55      className="tui-panel tui-user-panel"
 56    >
 57      <span className="tui-panel-title">{`─[ user · ${displayName} ]─`}</span>
 58
 59      <div className="tui-detail-grid tui-user-panel-grid">
 60        <Field label="user"     value={displayName}  emphasis="bright" />
 61        <Field label="username" value={username || <Dim>(none)</Dim>} />
 62        <Field label="email"    value={email || <Dim>(none)</Dim>} />
 63        <Field label="groups"   value={groups.length > 0 ? groups.join(", ") : <Dim>(none)</Dim>} />
 64        <div className="tui-detail-field tui-user-panel-sub">
 65          <span className="tui-detail-label">{"sub     "}</span>
 66          <span className="tui-detail-colon">:</span>{" "}
 67          <span className="tui-detail-value tui-user-panel-sub-value" title={user.sub}>{user.sub}</span>
 68        </div>
 69      </div>
 70
 71      <div className="tui-detail-actions">
 72        <button type="button" onClick={onSignOut} className="tui-detail-action tui-detail-action--danger">
 73          [s] sign out
 74        </button>
 75        <span className="tui-detail-sep">·</span>
 76        <button type="button" onClick={onClose} className="tui-detail-action">
 77          [Esc] close
 78        </button>
 79      </div>
 80    </div>
 81  );
 82}
 83
 084function Field({ label, value, emphasis }: {
 85  label: string;
 86  value: React.ReactNode;
 87  emphasis?: "bright";
 88}) {
 089  return (
 90    <div className="tui-detail-field">
 91      <span className="tui-detail-label">{label.padEnd(8, " ")}</span>
 92      <span className="tui-detail-colon">:</span>{" "}
 93      <span className={emphasis ? `tui-detail-value tui-detail-value--${emphasis}` : "tui-detail-value"}>
 94        {value}
 95      </span>
 96    </div>
 97  );
 98}
 99
 0100function Dim({ children }: { children: React.ReactNode }) {
 0101  return <span style={{ color: "var(--c-fg-3)" }}>{children}</span>;
 102}