uhc-portal-components-react 0.2.37 → 0.2.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.40 - 2026-11-04
6
+
7
+ - Keep credential QR, organization discovery and professional DID/URN as three
8
+ explicit evidence choices. Organization country and legal-identifier fields
9
+ no longer replace the manual DID/URN route.
10
+
11
+ ## 0.2.39 - 2026-11-04
12
+
13
+ - Let hosts offer country, optional region, organization legal identifier and
14
+ sector discovery for an existing professional. When several authoritative
15
+ roles resolve, require one to be selected before rendering device PIN setup.
16
+
17
+ ## 0.2.38 - 2026-11-03
18
+
19
+ - Keep the language dropdown inside the shared person menu on public and
20
+ authenticated portal routes.
21
+ - Preserve the same account panel before sign-in while omitting account data
22
+ and private preferences until a verified session exists.
23
+ - Let signed-in hosts opt into showing language alongside their account
24
+ preferences, and align the subject-card directory dependency with the
25
+ reusable provider selector contract.
3
26
  ## 0.2.37 - 2026-11-03
4
27
 
5
28
  - Split existing-professional registration into evidence verification and
package/README.md CHANGED
@@ -135,10 +135,13 @@ The package deliberately exposes modules instead of a cloned application:
135
135
  individual member; the host refreshes the authorized-subject list after its
136
136
  BFF accepts the invitation.
137
137
  - `ExistingProfessionalProfilePanel`: existing employees scan or upload a
138
- signed professional-credential QR, or use the explicit DID/URN plus sector
139
- fallback. When the host supplies `onResolve`, the screen verifies and shows
140
- the server-resolved role before it offers local device-PIN setup and DCR.
141
- This screen never accepts an employee invitation.
138
+ signed professional-credential QR. A host may also expose two separate
139
+ alternatives: join an existing organization by country, optional region,
140
+ legal identifier and sector, or enter a known professional DID/URN and
141
+ sector. Organization discovery never replaces the DID/URN fallback. When
142
+ the host supplies `onResolve`, the screen shows the server-resolved role
143
+ choices before it offers local device-PIN setup and DCR. This screen never
144
+ accepts an employee invitation or treats a browser role choice as authority.
142
145
  - `SubjectAccessRequestPanel`: controlled provider-enabled QR, numeric-code,
143
146
  `did:web`, microchip, tattoo, email or telephone locator. Resolution and
144
147
  returned identifier evidence never grant subject access.
@@ -4,6 +4,7 @@ export type ExistingProfessionalProfileCopy = Readonly<{
4
4
  title: string;
5
5
  description: string;
6
6
  credentialMethodLabel: string;
7
+ organizationMethodLabel: string;
7
8
  manualMethodLabel: string;
8
9
  scanProfessionalCredential: string;
9
10
  uploadProfessionalCredentialQr: string;
@@ -13,6 +14,12 @@ export type ExistingProfessionalProfileCopy = Readonly<{
13
14
  cameraActive: string;
14
15
  credentialQrUnreadable: string;
15
16
  professionalIdentifierLabel: string;
17
+ countryLabel?: string;
18
+ countryPlaceholder?: string;
19
+ regionalIdentifierLabel?: string;
20
+ regionLabel?: string;
21
+ regionPlaceholder?: string;
22
+ organizationIdentifierLabel?: string;
16
23
  sectorLabel: string;
17
24
  pinLabel: string;
18
25
  pinConfirmationLabel: string;
@@ -34,6 +41,14 @@ export type ExistingProfessionalProfileInput = Readonly<{
34
41
  professionalIdentifier: string;
35
42
  sector: string;
36
43
  pin: string;
44
+ }> | Readonly<{
45
+ method: 'organization';
46
+ country: string;
47
+ region?: string;
48
+ organizationIdentifier: string;
49
+ sector: string;
50
+ selectedProfile: string;
51
+ pin: string;
37
52
  }>;
38
53
  export type ExistingProfessionalEvidenceInput = Readonly<{
39
54
  method: 'credential';
@@ -42,9 +57,16 @@ export type ExistingProfessionalEvidenceInput = Readonly<{
42
57
  method: 'identifier';
43
58
  professionalIdentifier: string;
44
59
  sector: string;
60
+ }> | Readonly<{
61
+ method: 'organization';
62
+ country: string;
63
+ region?: string;
64
+ organizationIdentifier: string;
65
+ sector: string;
45
66
  }>;
46
67
  export type ResolvedExistingProfessionalProfile = Omit<ProfileProtectionTargetV2, 'eyebrow'> & Readonly<{
47
68
  eyebrow?: string;
69
+ selectionValue?: string;
48
70
  }>;
49
71
  export type ExistingProfessionalProfileRequest = Readonly<{
50
72
  professionalCredential: string;
@@ -53,6 +75,13 @@ export type ExistingProfessionalProfileRequest = Readonly<{
53
75
  professionalIdentifier: string;
54
76
  sectorHint: string;
55
77
  pin: string;
78
+ }> | Readonly<{
79
+ country: string;
80
+ region?: string;
81
+ organizationIdentifier: string;
82
+ sectorHint: string;
83
+ selectedProfile: string;
84
+ pin: string;
56
85
  }>;
57
86
  /** Shared BFF serialization keeps professional evidence distinct from an invitation code. */
58
87
  export declare function toExistingProfessionalProfileRequest(input: ExistingProfessionalProfileInput): ExistingProfessionalProfileRequest;
@@ -60,22 +89,28 @@ export type ExistingProfessionalSectorOption = Readonly<{
60
89
  value: string;
61
90
  label: string;
62
91
  }>;
92
+ export type ExistingProfessionalJurisdictionOption = Readonly<{
93
+ value: string;
94
+ label: string;
95
+ }>;
63
96
  export type ExistingProfessionalProfilePanelProps = Readonly<{
64
97
  copy: ExistingProfessionalProfileCopy;
65
98
  sectors: readonly ExistingProfessionalSectorOption[];
99
+ countries?: readonly ExistingProfessionalJurisdictionOption[];
100
+ regionsByCountry?: Readonly<Record<string, readonly ExistingProfessionalJurisdictionOption[]>>;
66
101
  busy: boolean;
67
102
  error?: string;
68
103
  onScanProfessionalCredential?: () => Promise<string | undefined>;
69
104
  onReadProfessionalCredentialImage?: (file: File) => Promise<string | undefined>;
70
- onResolve?: (input: ExistingProfessionalEvidenceInput) => Promise<ResolvedExistingProfessionalProfile>;
105
+ onResolve?: (input: ExistingProfessionalEvidenceInput) => Promise<ResolvedExistingProfessionalProfile | readonly ResolvedExistingProfessionalProfile[]>;
71
106
  onSubmit: (input: ExistingProfessionalProfileInput) => void | Promise<void>;
72
107
  }>;
73
108
  /**
74
109
  * Registers this portal device for an already-active professional profile.
75
110
  * Professional evidence, employee invitations and device registration are
76
111
  * separate artifacts. This screen accepts no invitation. A signed credential
77
- * is the preferred path; a single DID-or-URN field plus sector is the explicit
78
- * manual fallback and never becomes browser-selected authority by itself.
112
+ * is the preferred path. Organization coordinates and a DID-or-URN are two
113
+ * explicit alternatives; neither becomes browser-selected authority by itself.
79
114
  */
80
- export declare function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }: ExistingProfessionalProfilePanelProps): import("react").JSX.Element;
115
+ export declare function ExistingProfessionalProfilePanel({ copy, sectors, countries, regionsByCountry, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }: ExistingProfessionalProfilePanelProps): import("react").JSX.Element;
81
116
  //# sourceMappingURL=ExistingProfessionalProfilePanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExistingProfessionalProfilePanel.d.ts","sourceRoot":"","sources":["../src/ExistingProfessionalProfilePanel.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ExistingProfessionalProfilePanel.d.ts","sourceRoot":"","sources":["../src/ExistingProfessionalProfilePanel.tsx"],"names":[],"mappings":"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"}
@@ -6,25 +6,39 @@ import { ProfileIdentityTarget } from './ProfileIdentityTarget.js';
6
6
  const decodeQrImage = jsQrModule;
7
7
  /** Shared BFF serialization keeps professional evidence distinct from an invitation code. */
8
8
  export function toExistingProfessionalProfileRequest(input) {
9
- return input.method === 'credential'
10
- ? Object.freeze({ professionalCredential: input.professionalCredential, pin: input.pin })
11
- : Object.freeze({ professionalIdentifier: input.professionalIdentifier, sectorHint: input.sector, pin: input.pin });
9
+ if (input.method === 'credential')
10
+ return Object.freeze({ professionalCredential: input.professionalCredential, pin: input.pin });
11
+ if (input.method === 'identifier')
12
+ return Object.freeze({ professionalIdentifier: input.professionalIdentifier, sectorHint: input.sector, pin: input.pin });
13
+ return Object.freeze({
14
+ country: input.country,
15
+ ...(input.region ? { region: input.region } : {}),
16
+ organizationIdentifier: input.organizationIdentifier,
17
+ sectorHint: input.sector,
18
+ selectedProfile: input.selectedProfile,
19
+ pin: input.pin,
20
+ });
12
21
  }
13
22
  /**
14
23
  * Registers this portal device for an already-active professional profile.
15
24
  * Professional evidence, employee invitations and device registration are
16
25
  * separate artifacts. This screen accepts no invitation. A signed credential
17
- * is the preferred path; a single DID-or-URN field plus sector is the explicit
18
- * manual fallback and never becomes browser-selected authority by itself.
26
+ * is the preferred path. Organization coordinates and a DID-or-URN are two
27
+ * explicit alternatives; neither becomes browser-selected authority by itself.
19
28
  */
20
- export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }) {
29
+ export function ExistingProfessionalProfilePanel({ copy, sectors, countries = [], regionsByCountry = {}, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }) {
21
30
  const [method, setMethod] = useState('credential');
22
31
  const [professionalCredential, setProfessionalCredential] = useState('');
23
32
  const [professionalIdentifier, setProfessionalIdentifier] = useState('');
24
33
  const [sector, setSector] = useState(sectors[0]?.value || '');
34
+ const [country, setCountry] = useState('');
35
+ const [regional, setRegional] = useState(false);
36
+ const [region, setRegion] = useState('');
37
+ const [organizationIdentifier, setOrganizationIdentifier] = useState('');
25
38
  const [pin, setPin] = useState('');
26
39
  const [pinConfirmation, setPinConfirmation] = useState('');
27
- const [resolvedProfile, setResolvedProfile] = useState();
40
+ const [resolvedProfiles, setResolvedProfiles] = useState([]);
41
+ const [selectedProfile, setSelectedProfile] = useState();
28
42
  const [resolving, setResolving] = useState(false);
29
43
  const [localError, setLocalError] = useState('');
30
44
  const [cameraActive, setCameraActive] = useState(false);
@@ -116,6 +130,14 @@ export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, o
116
130
  }
117
131
  return { method, professionalCredential: professionalCredential.trim() };
118
132
  }
133
+ if (method === 'organization')
134
+ return {
135
+ method: 'organization',
136
+ country,
137
+ ...(regional ? { region } : {}),
138
+ organizationIdentifier: organizationIdentifier.trim(),
139
+ sector,
140
+ };
119
141
  return { method, professionalIdentifier: professionalIdentifier.trim(), sector };
120
142
  }
121
143
  async function submit(event) {
@@ -123,11 +145,14 @@ export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, o
123
145
  const evidence = evidenceInput();
124
146
  if (!evidence)
125
147
  return;
126
- if (onResolve && !resolvedProfile) {
148
+ if (onResolve && resolvedProfiles.length === 0) {
127
149
  setLocalError('');
128
150
  setResolving(true);
129
151
  try {
130
- setResolvedProfile(await onResolve(evidence));
152
+ const resolution = await onResolve(evidence);
153
+ const profiles = Array.isArray(resolution) ? resolution : [resolution];
154
+ setResolvedProfiles(profiles);
155
+ setSelectedProfile(profiles.length === 1 ? profiles[0] : undefined);
131
156
  }
132
157
  catch (reason) {
133
158
  setLocalError(reason instanceof Error ? reason.message : copy.verificationFailed || 'professional_profile_verification_failed');
@@ -146,15 +171,24 @@ export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, o
146
171
  void onSubmit({ ...evidence, pin });
147
172
  return;
148
173
  }
174
+ if (evidence.method === 'organization') {
175
+ if (!selectedProfile?.selectionValue) {
176
+ setLocalError(copy.verificationFailed || 'professional_profile_selection_required');
177
+ return;
178
+ }
179
+ void onSubmit({ ...evidence, selectedProfile: selectedProfile.selectionValue, pin });
180
+ return;
181
+ }
149
182
  void onSubmit({ ...evidence, pin });
150
183
  }
151
- if (onResolve && resolvedProfile)
152
- return _jsxs("section", { className: "uhcp-professional-activation uhcp-professional-pin-setup", "aria-labelledby": "uhcp-existing-professional-pin-title", children: [_jsxs("header", { children: [_jsx("h1", { id: "uhcp-existing-professional-pin-title", children: copy.verifiedProfile || copy.title }), _jsx("p", { children: copy.description })] }), _jsx(ProfileIdentityTarget, { target: { eyebrow: copy.verifiedProfile || '', ...resolvedProfile } }), _jsxs("form", { onSubmit: submit, children: [_jsxs("label", { children: [_jsx("span", { children: copy.pinLabel }), _jsx("input", { name: "newPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pin, onChange: event => setPin(event.currentTarget.value), required: true })] }), _jsxs("label", { children: [_jsx("span", { children: copy.pinConfirmationLabel }), _jsx("input", { name: "confirmPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pinConfirmation, onChange: event => setPinConfirmation(event.currentTarget.value), required: true })] }), (localError || error) ? _jsx("p", { role: "alert", children: localError || error }) : null, _jsx("button", { type: "submit", disabled: busy, children: busy ? copy.submitting : copy.submit }), _jsx("button", { type: "button", className: "uhcp-secondary-action", disabled: busy, onClick: () => { setResolvedProfile(undefined); setPin(''); setPinConfirmation(''); setLocalError(''); }, children: copy.changeEvidence || copy.manualMethodLabel })] })] });
153
- return _jsx("section", { className: "uhcp-professional-activation", "aria-labelledby": "uhcp-existing-professional-title", children: _jsxs("form", { onSubmit: submit, children: [_jsxs("header", { children: [copy.eyebrow ? _jsx("p", { className: "uhcp-eyebrow", children: copy.eyebrow }) : null, _jsx("h1", { id: "uhcp-existing-professional-title", children: copy.title }), _jsx("p", { children: copy.description })] }), _jsxs("fieldset", { className: "uhcp-professional-evidence-methods", children: [_jsxs("label", { children: [_jsx("input", { type: "radio", name: "professionalEvidenceMethod", value: "credential", checked: method === 'credential', onChange: () => { setMethod('credential'); setLocalError(''); } }), _jsx("span", { children: copy.credentialMethodLabel })] }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "professionalEvidenceMethod", value: "identifier", checked: method === 'identifier', onChange: () => { setMethod('identifier'); setLocalError(''); } }), _jsx("span", { children: copy.manualMethodLabel })] })] }), method === 'credential'
184
+ if (onResolve && resolvedProfiles.length)
185
+ return _jsxs("section", { className: "uhcp-professional-activation uhcp-professional-pin-setup", "aria-labelledby": "uhcp-existing-professional-pin-title", children: [_jsxs("header", { children: [_jsx("h1", { id: "uhcp-existing-professional-pin-title", children: copy.verifiedProfile || copy.title }), _jsx("p", { children: copy.description })] }), resolvedProfiles.length > 1 ? _jsx("div", { className: "uhcp-resolved-professional-options", role: "group", "aria-label": copy.verifiedProfile || copy.title, children: resolvedProfiles.map(profile => _jsx("button", { type: "button", "aria-pressed": selectedProfile === profile, onClick: () => setSelectedProfile(profile), children: _jsx(ProfileIdentityTarget, { target: { eyebrow: copy.verifiedProfile || '', ...profile } }) }, profile.selectionValue || profile.title)) }) : selectedProfile ? _jsx(ProfileIdentityTarget, { target: { eyebrow: copy.verifiedProfile || '', ...selectedProfile } }) : null, _jsxs("form", { onSubmit: submit, children: [selectedProfile ? _jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: copy.pinLabel }), _jsx("input", { name: "newPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pin, onChange: event => setPin(event.currentTarget.value), required: true })] }), _jsxs("label", { children: [_jsx("span", { children: copy.pinConfirmationLabel }), _jsx("input", { name: "confirmPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pinConfirmation, onChange: event => setPinConfirmation(event.currentTarget.value), required: true })] })] }) : null, (localError || error) ? _jsx("p", { role: "alert", children: localError || error }) : null, selectedProfile ? _jsx("button", { type: "submit", disabled: busy, children: busy ? copy.submitting : copy.submit }) : null, _jsx("button", { type: "button", className: "uhcp-secondary-action", disabled: busy, onClick: () => { setResolvedProfiles([]); setSelectedProfile(undefined); setPin(''); setPinConfirmation(''); setLocalError(''); }, children: copy.changeEvidence || copy.manualMethodLabel })] })] });
186
+ return _jsx("section", { className: "uhcp-professional-activation", "aria-labelledby": "uhcp-existing-professional-title", children: _jsxs("form", { onSubmit: submit, children: [_jsxs("header", { children: [copy.eyebrow ? _jsx("p", { className: "uhcp-eyebrow", children: copy.eyebrow }) : null, _jsx("h1", { id: "uhcp-existing-professional-title", children: copy.title }), _jsx("p", { children: copy.description })] }), _jsxs("fieldset", { className: "uhcp-professional-evidence-methods", children: [_jsxs("label", { children: [_jsx("input", { type: "radio", name: "professionalEvidenceMethod", value: "credential", checked: method === 'credential', onChange: () => { setMethod('credential'); setLocalError(''); } }), _jsx("span", { children: copy.credentialMethodLabel })] }), countries.length ? _jsxs("label", { children: [_jsx("input", { type: "radio", name: "professionalEvidenceMethod", value: "organization", checked: method === 'organization', onChange: () => { setMethod('organization'); setLocalError(''); } }), _jsx("span", { children: copy.organizationMethodLabel })] }) : null, _jsxs("label", { children: [_jsx("input", { type: "radio", name: "professionalEvidenceMethod", value: "identifier", checked: method === 'identifier', onChange: () => { setMethod('identifier'); setLocalError(''); } }), _jsx("span", { children: copy.manualMethodLabel })] })] }), method === 'credential'
154
187
  ? _jsxs("div", { className: "uhcp-professional-credential-capture", children: [_jsx("button", { type: "button", disabled: busy, onClick: () => void captureCredential(scanWithCamera).catch(() => setLocalError(copy.professionalCredentialRequired)), children: copy.scanProfessionalCredential }), _jsxs("label", { children: [_jsx("span", { children: copy.uploadProfessionalCredentialQr }), _jsx("input", { name: "professionalCredentialQrImage", type: "file", accept: "image/png,image/jpeg,image/webp", disabled: busy, onChange: event => {
155
188
  const file = event.currentTarget.files?.[0];
156
189
  if (file)
157
190
  void captureCredential(() => readCredentialImage(file)).catch(() => setLocalError(copy.credentialQrUnreadable));
158
191
  } })] }), cameraActive ? _jsxs(_Fragment, { children: [_jsx("video", { ref: videoRef, playsInline: true, muted: true, "aria-label": copy.cameraActive }), _jsx("p", { role: "status", children: copy.cameraActive }), _jsx("button", { type: "button", onClick: () => stopCamera(), children: copy.closeCamera })] }) : _jsx("video", { ref: videoRef, hidden: true, playsInline: true, muted: true }), _jsx("canvas", { ref: canvasRef, hidden: true, "aria-hidden": "true" }), professionalCredential ? _jsx("p", { role: "status", children: copy.professionalCredentialReady }) : null] })
159
- : _jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: copy.professionalIdentifierLabel }), _jsx("input", { name: "professionalIdentifier", value: professionalIdentifier, onChange: event => setProfessionalIdentifier(event.currentTarget.value), autoComplete: "off", required: true })] }), _jsxs("label", { children: [_jsx("span", { children: copy.sectorLabel }), _jsx("select", { name: "professionalSector", value: sector, onChange: event => setSector(event.currentTarget.value), required: true, children: sectors.map(option => _jsx("option", { value: option.value, children: option.label }, option.value)) })] })] }), !onResolve ? _jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: copy.pinLabel }), _jsx("input", { name: "newPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pin, onChange: event => setPin(event.currentTarget.value), required: true })] }), _jsxs("label", { children: [_jsx("span", { children: copy.pinConfirmationLabel }), _jsx("input", { name: "confirmPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pinConfirmation, onChange: event => setPinConfirmation(event.currentTarget.value), required: true })] })] }) : null, (localError || error) ? _jsx("p", { role: "alert", children: localError || error }) : null, _jsx("button", { type: "submit", disabled: busy || resolving, children: resolving ? copy.verifying || copy.submitting : onResolve ? copy.verify || copy.submit : busy ? copy.submitting : copy.submit })] }) });
192
+ : _jsxs(_Fragment, { children: [method === 'organization' ? _jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: copy.countryLabel || 'Country' }), _jsxs("select", { name: "professionalCountry", value: country, onChange: event => { setCountry(event.currentTarget.value); setRegional(false); setRegion(''); }, required: true, children: [_jsx("option", { value: "", disabled: true, children: copy.countryPlaceholder || copy.countryLabel || 'Country' }), countries.map(option => _jsx("option", { value: option.value, children: option.label }, option.value))] })] }), _jsxs("label", { className: "uhcp-professional-regional-identifier", children: [_jsx("input", { type: "checkbox", checked: regional, disabled: !country || !(regionsByCountry[country]?.length), onChange: event => { setRegional(event.currentTarget.checked); if (!event.currentTarget.checked)
193
+ setRegion(''); } }), _jsx("span", { children: copy.regionalIdentifierLabel || 'The legal identifier is regional' })] }), regional ? _jsxs("label", { children: [_jsx("span", { children: copy.regionLabel || 'Region' }), _jsxs("select", { name: "professionalRegion", value: region, onChange: event => setRegion(event.currentTarget.value), required: true, children: [_jsx("option", { value: "", disabled: true, children: copy.regionPlaceholder || copy.regionLabel || 'Region' }), (regionsByCountry[country] || []).map(option => _jsx("option", { value: option.value, children: option.label }, option.value))] })] }) : null, _jsxs("label", { children: [_jsx("span", { children: copy.organizationIdentifierLabel || 'Organization legal identifier' }), _jsx("input", { name: "organizationIdentifier", value: organizationIdentifier, onChange: event => setOrganizationIdentifier(event.currentTarget.value), autoComplete: "off", required: true })] })] }) : _jsxs("label", { children: [_jsx("span", { children: copy.professionalIdentifierLabel }), _jsx("input", { name: "professionalIdentifier", value: professionalIdentifier, onChange: event => setProfessionalIdentifier(event.currentTarget.value), autoComplete: "off", required: true })] }), _jsxs("label", { children: [_jsx("span", { children: copy.sectorLabel }), _jsx("select", { name: "professionalSector", value: sector, onChange: event => setSector(event.currentTarget.value), required: true, children: sectors.map(option => _jsx("option", { value: option.value, children: option.label }, option.value)) })] })] }), !onResolve ? _jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: copy.pinLabel }), _jsx("input", { name: "newPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pin, onChange: event => setPin(event.currentTarget.value), required: true })] }), _jsxs("label", { children: [_jsx("span", { children: copy.pinConfirmationLabel }), _jsx("input", { name: "confirmPin", type: "password", inputMode: "numeric", minLength: 6, maxLength: 12, autoComplete: "new-password", value: pinConfirmation, onChange: event => setPinConfirmation(event.currentTarget.value), required: true })] })] }) : null, (localError || error) ? _jsx("p", { role: "alert", children: localError || error }) : null, _jsx("button", { type: "submit", disabled: busy || resolving, children: resolving ? copy.verifying || copy.submitting : onResolve ? copy.verify || copy.submit : busy ? copy.submitting : copy.submit })] }) });
160
194
  }
@@ -6,6 +6,7 @@ export type PortalHeaderProps = Readonly<{
6
6
  accountIdentifier?: string | null;
7
7
  languageHref?: string;
8
8
  languageOptions?: readonly PortalLanguageOption[];
9
+ languageInPreferences?: boolean;
9
10
  guestActionHref: string;
10
11
  accountOptions?: readonly PortalAccountOption[];
11
12
  onSelectAccount?: (id: string) => void;
@@ -21,5 +22,5 @@ export type PortalHeaderProps = Readonly<{
21
22
  * Authentication is deliberately supplied as data and callbacks. The
22
23
  * component never imports Firebase and never treats browser state as a role.
23
24
  */
24
- export declare function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions, guestActionHref, accountOptions, onSelectAccount, menuActions, onSignOut, onThemeChange, storageNamespace, currentSubject, }: PortalHeaderProps): import("react").JSX.Element;
25
+ export declare function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions, languageInPreferences, guestActionHref, accountOptions, onSelectAccount, menuActions, onSignOut, onThemeChange, storageNamespace, currentSubject, }: PortalHeaderProps): import("react").JSX.Element;
25
26
  //# sourceMappingURL=PortalHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PortalHeader.d.ts","sourceRoot":"","sources":["../src/PortalHeader.tsx"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EAEpB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAA;AAQvB,OAAO,EAGL,KAAK,gCAAgC,EACtC,MAAM,2BAA2B,CAAA;AAElC,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,WAAW,CAAA;IAClB,IAAI,EAAE,eAAe,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACjD,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAA;IAC/C,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,WAAW,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACzC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,CAAC,EAAE,gCAAgC,CAAA;CAClD,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,iBAAiB,EACjB,YAAY,EACZ,eAAoB,EACpB,eAAe,EACf,cAAmB,EACnB,eAAe,EACf,WAAgB,EAChB,SAAS,EACT,aAAa,EACb,gBAA+B,EAC/B,cAAc,GACf,EAAE,iBAAiB,+BAgMnB"}
1
+ {"version":3,"file":"PortalHeader.d.ts","sourceRoot":"","sources":["../src/PortalHeader.tsx"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EAEpB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAA;AAQvB,OAAO,EAGL,KAAK,gCAAgC,EACtC,MAAM,2BAA2B,CAAA;AAElC,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,WAAW,CAAA;IAClB,IAAI,EAAE,eAAe,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACjD,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAA;IAC/C,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,WAAW,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACzC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,CAAC,EAAE,gCAAgC,CAAA;CAClD,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,iBAAiB,EACjB,YAAY,EACZ,eAAoB,EACpB,qBAA6B,EAC7B,eAAe,EACf,cAAmB,EACnB,eAAe,EACf,WAAgB,EAChB,SAAS,EACT,aAAa,EACb,gBAA+B,EAC/B,cAAc,GACf,EAAE,iBAAiB,+BAwMnB"}
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { portalUiDensities, portalUiThemes, } from './contracts.js';
5
5
  import { resolvePortalUiDensity, resolvePortalUiTheme, resolveRenderedPortalTheme, } from './preferences.js';
@@ -12,13 +12,15 @@ import { PortalCurrentSubjectControl, PortalCurrentSubjectSlot, } from './Portal
12
12
  * Authentication is deliberately supplied as data and callbacks. The
13
13
  * component never imports Firebase and never treats browser state as a role.
14
14
  */
15
- export function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions = [], guestActionHref, accountOptions = [], onSelectAccount, menuActions = [], onSignOut, onThemeChange, storageNamespace = 'uhc-portal', currentSubject, }) {
15
+ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions = [], languageInPreferences = false, guestActionHref, accountOptions = [], onSelectAccount, menuActions = [], onSignOut, onThemeChange, storageNamespace = 'uhc-portal', currentSubject, }) {
16
16
  const [open, setOpen] = useState(false);
17
17
  const [density, setDensity] = useState('medium');
18
18
  const [theme, setTheme] = useState('system');
19
19
  const ref = useRef(null);
20
20
  const densityKey = `${storageNamespace}.density`;
21
21
  const themeKey = `${storageNamespace}.theme`;
22
+ const signedIn = Boolean(accountIdentifier);
23
+ const menuLabel = signedIn ? copy.accountAndPreferences : copy.chooseAccess;
22
24
  useEffect(() => {
23
25
  const savedDensity = resolvePortalUiDensity(localStorage.getItem(densityKey));
24
26
  const savedTheme = resolvePortalUiTheme(localStorage.getItem(themeKey));
@@ -69,12 +71,15 @@ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, lan
69
71
  }
70
72
  return (_jsxs("header", { className: "uhcp-header", style: brand.variables, children: [brand.mark && brand.markHref ? (_jsxs("div", { className: "uhcp-brand", children: [_jsx("a", { className: "uhcp-brand-mark-link", href: brand.markHref, "aria-label": brand.productName, children: _jsx(PortalBrandArtwork, { artwork: brand.mark, className: "uhcp-brand-mark" }) }), _jsx("a", { className: "uhcp-brand-copy", href: brand.homeHref, "aria-label": copy.home, children: _jsxs("span", { children: [_jsx("strong", { children: brand.productName }), _jsx("small", { className: accountIdentifier ? 'uhcp-brand-account' : undefined, children: accountIdentifier || brand.organizationName })] }) })] })) : (_jsxs("a", { className: "uhcp-brand", href: brand.homeHref, "aria-label": copy.home, children: [brand.mark ? (_jsx(PortalBrandArtwork, { artwork: brand.mark, className: "uhcp-brand-mark" })) : null, _jsxs("span", { children: [_jsx("strong", { children: brand.productName }), _jsx("small", { className: accountIdentifier ? 'uhcp-brand-account' : undefined, children: accountIdentifier || brand.organizationName })] })] })), currentSubject
71
73
  ? _jsx(PortalCurrentSubjectControl, { ...currentSubject })
72
- : _jsx(PortalCurrentSubjectSlot, {}), _jsxs("div", { className: "uhcp-account-menu", ref: ref, children: [_jsx("button", { type: "button", className: "uhcp-trigger", onClick: () => setOpen((value) => !value), "aria-label": copy.accountAndPreferences, "aria-expanded": open, children: _jsx(ProfileIdentityIcon, {}) }), open && (_jsxs("section", { className: "uhcp-menu", "aria-label": copy.accountAndPreferences, children: [_jsxs("div", { className: "uhcp-account-state", children: [_jsx("strong", { children: copy.accountAndPreferences }), _jsx("small", { children: accountIdentifier || copy.notSignedIn })] }), accountOptions.length > 0 && (_jsx("div", { className: "uhcp-account-options", children: accountOptions.map((option) => (_jsxs("button", { type: "button", disabled: option.disabled, "aria-pressed": Boolean(option.selected), onClick: () => {
74
+ : _jsx(PortalCurrentSubjectSlot, {}), _jsxs("div", { className: "uhcp-account-menu", ref: ref, children: [_jsx("button", { type: "button", className: "uhcp-trigger", onClick: () => setOpen((value) => !value), "aria-label": menuLabel, "aria-expanded": open, children: _jsx(ProfileIdentityIcon, {}) }), open && (_jsxs("section", { className: "uhcp-menu", "aria-label": menuLabel, children: [_jsxs("div", { className: "uhcp-account-state", children: [_jsx("strong", { children: menuLabel }), _jsx("small", { children: accountIdentifier || copy.notSignedIn })] }), signedIn && accountOptions.length > 0 && (_jsx("div", { className: "uhcp-account-options", children: accountOptions.map((option) => (_jsxs("button", { type: "button", disabled: option.disabled, "aria-pressed": Boolean(option.selected), onClick: () => {
73
75
  onSelectAccount?.(option.id);
74
76
  setOpen(false);
75
- }, children: [_jsx("span", { children: option.label }), option.description && _jsx("small", { children: option.description })] }, option.id))) })), _jsx(PreferenceGroup, { label: copy.appearance, children: portalUiThemes.map((value) => (_jsxs("button", { type: "button", "aria-pressed": theme === value, onClick: () => changeTheme(value), children: [_jsx("span", { "aria-hidden": "true", children: value === 'light' ? '☀' : value === 'dark' ? '☾' : '◫' }), copy.themes[value]] }, value))) }), _jsx(PreferenceGroup, { label: copy.textSize, children: portalUiDensities.map((value) => (_jsx("button", { type: "button", "aria-pressed": density === value, onClick: () => changeDensity(value), children: value === 'small' ? 'S' : value === 'medium' ? 'M' : 'L' }, value))) }), _jsxs("div", { className: "uhcp-menu-actions", children: [languageOptions.length > 0
76
- ? languageOptions.map((option) => (_jsxs("a", { href: option.href, hrefLang: option.id, "aria-current": option.current ? 'page' : undefined, onClick: () => setOpen(false), children: ["\u25CE ", option.label] }, option.id)))
77
- : languageHref && _jsxs("a", { href: languageHref, onClick: () => setOpen(false), children: ["\u25CE ", copy.language] }), menuActions.map((action) => action.href
77
+ }, children: [_jsx("span", { children: option.label }), option.description && _jsx("small", { children: option.description })] }, option.id))) })), signedIn ? _jsxs(_Fragment, { children: [_jsx(PreferenceGroup, { label: copy.appearance, children: portalUiThemes.map((value) => (_jsxs("button", { type: "button", "aria-pressed": theme === value, onClick: () => changeTheme(value), children: [_jsx("span", { "aria-hidden": "true", children: value === 'light' ? '☀' : value === 'dark' ? '☾' : '◫' }), copy.themes[value]] }, value))) }), _jsx(PreferenceGroup, { label: copy.textSize, children: portalUiDensities.map((value) => (_jsx("button", { type: "button", "aria-pressed": density === value, onClick: () => changeDensity(value), children: value === 'small' ? 'S' : value === 'medium' ? 'M' : 'L' }, value))) })] }) : null, _jsxs("div", { className: "uhcp-menu-actions", children: [(!signedIn || languageInPreferences) && languageOptions.length > 0
78
+ ? _jsxs("label", { className: "uhcp-language-select", children: [_jsx("span", { children: copy.language }), _jsx("select", { "aria-label": copy.language, value: languageOptions.find((option) => option.current)?.href ?? languageOptions[0]?.href, onChange: (event) => {
79
+ setOpen(false);
80
+ window.location.assign(event.currentTarget.value);
81
+ }, children: languageOptions.map((option) => (_jsx("option", { value: option.href, children: option.label }, option.id))) })] })
82
+ : (!signedIn || languageInPreferences) && languageHref && _jsxs("a", { href: languageHref, onClick: () => setOpen(false), children: ["\u25CE ", copy.language] }), signedIn && menuActions.map((action) => action.href
78
83
  ? (_jsx("a", { href: action.href, onClick: () => setOpen(false), children: action.label }, action.id))
79
84
  : (_jsx("button", { type: "button", onClick: () => {
80
85
  setOpen(false);
package/dist/styles.css CHANGED
@@ -169,6 +169,8 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
169
169
  width: 100%;
170
170
  }
171
171
  .uhcp-menu-actions a:hover, .uhcp-menu-actions button:hover { background: var(--uhcp-surface-muted); color: var(--uhcp-turquoise); }
172
+ .uhcp-language-select { align-items: center; color: var(--uhcp-text); display: grid; font-weight: 700; gap: .45rem; grid-template-columns: auto minmax(0, 1fr); padding: .45rem .7rem; }
173
+ .uhcp-language-select select { background: var(--uhcp-panel); border: 1px solid var(--uhcp-border); border-radius: .75rem; color: var(--uhcp-text); font: inherit; min-width: 0; padding: .6rem 2rem .6rem .7rem; width: 100%; }
172
174
  .uhcp-profile-gate {
173
175
  background: var(--uhcp-panel);
174
176
  border: 1px solid var(--uhcp-border);
@@ -216,6 +218,11 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
216
218
  .uhcp-professional-evidence-methods { border: 0; display: grid; gap: .75rem; margin: 0; padding: 0; }
217
219
  .uhcp-professional-evidence-methods label { align-items: center; border: 1px solid var(--uhcp-border); border-radius: .9rem; cursor: pointer; display: grid; gap: .75rem; grid-template-columns: 1.25rem 1fr; padding: .9rem 1rem; }
218
220
  .uhcp-professional-evidence-methods input[type='radio'] { accent-color: var(--uhcp-turquoise); height: 1.1rem; margin: 0; width: 1.1rem; }
221
+ .uhcp-professional-regional-identifier { align-items: center !important; display: flex !important; gap: .55rem; }
222
+ .uhcp-professional-regional-identifier input { accent-color: var(--uhcp-turquoise); min-height: auto !important; width: auto !important; }
223
+ .uhcp-resolved-professional-options { display: grid; gap: .75rem; }
224
+ .uhcp-resolved-professional-options > button { background: transparent; border: 1px solid var(--uhcp-border); border-radius: 1rem; color: inherit; cursor: pointer; padding: 0; text-align: left; }
225
+ .uhcp-resolved-professional-options > button[aria-pressed='true'] { border-color: var(--uhcp-turquoise); box-shadow: 0 0 0 2px color-mix(in srgb, var(--uhcp-turquoise) 25%, transparent); }
219
226
  .uhcp-professional-pin-setup { display: grid; gap: 1rem; }
220
227
  .uhcp-professional-pin-setup > header h1, .uhcp-professional-pin-setup > header p { margin: 0; }
221
228
  .uhcp-professional-pin-setup > header p { margin-top: .45rem; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.2.37",
3
+ "version": "0.2.40",
4
4
  "description": "Brand-neutral React portal screens, account preferences and protected-profile UI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "fhir-data-utils-ts": "0.6.21",
35
35
  "gdc-common-utils-ts": "2.9.30",
36
36
  "jsqr": "1.4.0",
37
- "sos-data-utils-ts": "0.6.15",
37
+ "sos-data-utils-ts": "0.6.21",
38
38
  "terminology-components-react": "0.2.0"
39
39
  },
40
40
  "devDependencies": {