File size: 2,659 Bytes
c9a1ce7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// The reviewer session identity: the name and role every confirmation is attributed to. It comes from
// the per-user login (#128), set once when a reviewer signs in (LoginBar.tsx) and cleared on sign-out,
// not a field re-entered on each decision. PRD sections 4.4 and 8.4: each confirmed value records who
// confirmed it and in what role, so the identity has to be the session's.
//
// localStorage rather than sessionStorage, matching the session token: a reload should not drop a
// reviewer out of their session. The corpus is public de-identified TCGA data, and this is a
// local-first tool.
//
// Identity changes are observable. setIdentity and clearIdentity dispatch a window CustomEvent so a
// screen reflects login and logout without a reload; subscribeIdentity also listens for the cross-tab
// "storage" event so a sign-in in another tab updates this one.

const NAME_KEY = "endopath.reviewerName";
const ROLE_KEY = "endopath.reviewerRole";
const IDENTITY_EVENT = "endopath:identity";

// The roles PRD section 4.4 distinguishes: a licensed pathologist confirms the pathology variables; a
// data manager or curator abstracts the rest. Offered as a fixed set so the recorded role reads
// consistently across the ledger.
export const REVIEWER_ROLES = ["pathologist", "data manager", "curator"] as const;
export type ReviewerRole = (typeof REVIEWER_ROLES)[number];

export interface ReviewerIdentity {
  name: string;
  role: string;
}

export function getIdentity(): ReviewerIdentity {
  return {
    name: localStorage.getItem(NAME_KEY) ?? "",
    role: localStorage.getItem(ROLE_KEY) ?? "",
  };
}

export function setIdentity(identity: ReviewerIdentity): void {
  localStorage.setItem(NAME_KEY, identity.name.trim());
  localStorage.setItem(ROLE_KEY, identity.role.trim());
  window.dispatchEvent(new CustomEvent(IDENTITY_EVENT));
}

export function clearIdentity(): void {
  localStorage.removeItem(NAME_KEY);
  localStorage.removeItem(ROLE_KEY);
  window.dispatchEvent(new CustomEvent(IDENTITY_EVENT));
}

export function hasIdentity(): boolean {
  const { name, role } = getIdentity();
  return name.trim().length > 0 && role.trim().length > 0;
}

// Subscribe to identity changes: the in-tab CustomEvent from setIdentity/clearIdentity, and the
// cross-tab "storage" event so a sign-in or sign-out in another tab updates this one. Returns an
// unsubscribe.
export function subscribeIdentity(cb: () => void): () => void {
  window.addEventListener(IDENTITY_EVENT, cb);
  window.addEventListener("storage", cb);
  return () => {
    window.removeEventListener(IDENTITY_EVENT, cb);
    window.removeEventListener("storage", cb);
  };
}