uhc-portal-components-react 0.2.36 → 0.2.39
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 +25 -0
- package/README.md +6 -3
- package/dist/ExistingProfessionalProfilePanel.d.ts +53 -1
- package/dist/ExistingProfessionalProfilePanel.d.ts.map +1 -1
- package/dist/ExistingProfessionalProfilePanel.js +74 -12
- package/dist/PortalHeader.d.ts +2 -1
- package/dist/PortalHeader.d.ts.map +1 -1
- package/dist/PortalHeader.js +13 -10
- package/dist/ProfileIdentityTarget.d.ts +7 -0
- package/dist/ProfileIdentityTarget.d.ts.map +1 -0
- package/dist/ProfileIdentityTarget.js +10 -0
- package/dist/ProfileProtectionGateV2.d.ts.map +1 -1
- package/dist/ProfileProtectionGateV2.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/styles.css +16 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.2.39 - 2026-11-04
|
|
6
|
+
|
|
7
|
+
- Let hosts offer country, optional region, organization legal identifier and
|
|
8
|
+
sector discovery for an existing professional. When several authoritative
|
|
9
|
+
roles resolve, require one to be selected before rendering device PIN setup.
|
|
10
|
+
|
|
11
|
+
## 0.2.38 - 2026-11-03
|
|
12
|
+
|
|
13
|
+
- Keep the language dropdown inside the shared person menu on public and
|
|
14
|
+
authenticated portal routes.
|
|
15
|
+
- Preserve the same account panel before sign-in while omitting account data
|
|
16
|
+
and private preferences until a verified session exists.
|
|
17
|
+
- Let signed-in hosts opt into showing language alongside their account
|
|
18
|
+
preferences, and align the subject-card directory dependency with the
|
|
19
|
+
reusable provider selector contract.
|
|
20
|
+
## 0.2.37 - 2026-11-03
|
|
21
|
+
|
|
22
|
+
- Split existing-professional registration into evidence verification and
|
|
23
|
+
device-PIN setup so the verified role and organization are visible before a
|
|
24
|
+
local profile can be protected.
|
|
25
|
+
- Reuse one identity icon and resolved-profile target across the portal header,
|
|
26
|
+
profile unlock and professional registration screens.
|
|
27
|
+
|
|
3
28
|
## 0.2.36 - 2026-11-03
|
|
4
29
|
|
|
5
30
|
- Add an accessible shared card-provider selector that returns only the opaque
|
package/README.md
CHANGED
|
@@ -135,9 +135,12 @@ 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
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
signed professional-credential QR. A host may instead supply country and
|
|
139
|
+
regional-jurisdiction options so the employee enters an organization legal
|
|
140
|
+
identifier and sector without knowing a DID, URN or route. When the host
|
|
141
|
+
supplies `onResolve`, the screen shows the server-resolved role choices
|
|
142
|
+
before it offers local device-PIN setup and DCR. This screen never accepts an
|
|
143
|
+
employee invitation or treats a browser role choice as authority.
|
|
141
144
|
- `SubjectAccessRequestPanel`: controlled provider-enabled QR, numeric-code,
|
|
142
145
|
`did:web`, microchip, tattoo, email or telephone locator. Resolution and
|
|
143
146
|
returned identifier evidence never grant subject access.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProfileProtectionTargetV2 } from './contracts.js';
|
|
1
2
|
export type ExistingProfessionalProfileCopy = Readonly<{
|
|
2
3
|
eyebrow?: string;
|
|
3
4
|
title: string;
|
|
@@ -12,10 +13,21 @@ export type ExistingProfessionalProfileCopy = Readonly<{
|
|
|
12
13
|
cameraActive: string;
|
|
13
14
|
credentialQrUnreadable: string;
|
|
14
15
|
professionalIdentifierLabel: string;
|
|
16
|
+
countryLabel?: string;
|
|
17
|
+
countryPlaceholder?: string;
|
|
18
|
+
regionalIdentifierLabel?: string;
|
|
19
|
+
regionLabel?: string;
|
|
20
|
+
regionPlaceholder?: string;
|
|
21
|
+
organizationIdentifierLabel?: string;
|
|
15
22
|
sectorLabel: string;
|
|
16
23
|
pinLabel: string;
|
|
17
24
|
pinConfirmationLabel: string;
|
|
18
25
|
pinMismatch: string;
|
|
26
|
+
verify?: string;
|
|
27
|
+
verifying?: string;
|
|
28
|
+
verifiedProfile?: string;
|
|
29
|
+
changeEvidence?: string;
|
|
30
|
+
verificationFailed?: string;
|
|
19
31
|
submit: string;
|
|
20
32
|
submitting: string;
|
|
21
33
|
}>;
|
|
@@ -28,6 +40,32 @@ export type ExistingProfessionalProfileInput = Readonly<{
|
|
|
28
40
|
professionalIdentifier: string;
|
|
29
41
|
sector: string;
|
|
30
42
|
pin: string;
|
|
43
|
+
}> | Readonly<{
|
|
44
|
+
method: 'organization';
|
|
45
|
+
country: string;
|
|
46
|
+
region?: string;
|
|
47
|
+
organizationIdentifier: string;
|
|
48
|
+
sector: string;
|
|
49
|
+
selectedProfile: string;
|
|
50
|
+
pin: string;
|
|
51
|
+
}>;
|
|
52
|
+
export type ExistingProfessionalEvidenceInput = Readonly<{
|
|
53
|
+
method: 'credential';
|
|
54
|
+
professionalCredential: string;
|
|
55
|
+
}> | Readonly<{
|
|
56
|
+
method: 'identifier';
|
|
57
|
+
professionalIdentifier: string;
|
|
58
|
+
sector: string;
|
|
59
|
+
}> | Readonly<{
|
|
60
|
+
method: 'organization';
|
|
61
|
+
country: string;
|
|
62
|
+
region?: string;
|
|
63
|
+
organizationIdentifier: string;
|
|
64
|
+
sector: string;
|
|
65
|
+
}>;
|
|
66
|
+
export type ResolvedExistingProfessionalProfile = Omit<ProfileProtectionTargetV2, 'eyebrow'> & Readonly<{
|
|
67
|
+
eyebrow?: string;
|
|
68
|
+
selectionValue?: string;
|
|
31
69
|
}>;
|
|
32
70
|
export type ExistingProfessionalProfileRequest = Readonly<{
|
|
33
71
|
professionalCredential: string;
|
|
@@ -36,6 +74,13 @@ export type ExistingProfessionalProfileRequest = Readonly<{
|
|
|
36
74
|
professionalIdentifier: string;
|
|
37
75
|
sectorHint: string;
|
|
38
76
|
pin: string;
|
|
77
|
+
}> | Readonly<{
|
|
78
|
+
country: string;
|
|
79
|
+
region?: string;
|
|
80
|
+
organizationIdentifier: string;
|
|
81
|
+
sectorHint: string;
|
|
82
|
+
selectedProfile: string;
|
|
83
|
+
pin: string;
|
|
39
84
|
}>;
|
|
40
85
|
/** Shared BFF serialization keeps professional evidence distinct from an invitation code. */
|
|
41
86
|
export declare function toExistingProfessionalProfileRequest(input: ExistingProfessionalProfileInput): ExistingProfessionalProfileRequest;
|
|
@@ -43,13 +88,20 @@ export type ExistingProfessionalSectorOption = Readonly<{
|
|
|
43
88
|
value: string;
|
|
44
89
|
label: string;
|
|
45
90
|
}>;
|
|
91
|
+
export type ExistingProfessionalJurisdictionOption = Readonly<{
|
|
92
|
+
value: string;
|
|
93
|
+
label: string;
|
|
94
|
+
}>;
|
|
46
95
|
export type ExistingProfessionalProfilePanelProps = Readonly<{
|
|
47
96
|
copy: ExistingProfessionalProfileCopy;
|
|
48
97
|
sectors: readonly ExistingProfessionalSectorOption[];
|
|
98
|
+
countries?: readonly ExistingProfessionalJurisdictionOption[];
|
|
99
|
+
regionsByCountry?: Readonly<Record<string, readonly ExistingProfessionalJurisdictionOption[]>>;
|
|
49
100
|
busy: boolean;
|
|
50
101
|
error?: string;
|
|
51
102
|
onScanProfessionalCredential?: () => Promise<string | undefined>;
|
|
52
103
|
onReadProfessionalCredentialImage?: (file: File) => Promise<string | undefined>;
|
|
104
|
+
onResolve?: (input: ExistingProfessionalEvidenceInput) => Promise<ResolvedExistingProfessionalProfile | readonly ResolvedExistingProfessionalProfile[]>;
|
|
53
105
|
onSubmit: (input: ExistingProfessionalProfileInput) => void | Promise<void>;
|
|
54
106
|
}>;
|
|
55
107
|
/**
|
|
@@ -59,5 +111,5 @@ export type ExistingProfessionalProfilePanelProps = Readonly<{
|
|
|
59
111
|
* is the preferred path; a single DID-or-URN field plus sector is the explicit
|
|
60
112
|
* manual fallback and never becomes browser-selected authority by itself.
|
|
61
113
|
*/
|
|
62
|
-
export declare function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onSubmit }: ExistingProfessionalProfilePanelProps): import("react").JSX.Element;
|
|
114
|
+
export declare function ExistingProfessionalProfilePanel({ copy, sectors, countries, regionsByCountry, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }: ExistingProfessionalProfilePanelProps): import("react").JSX.Element;
|
|
63
115
|
//# sourceMappingURL=ExistingProfessionalProfilePanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExistingProfessionalProfilePanel.d.ts","sourceRoot":"","sources":["../src/ExistingProfessionalProfilePanel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExistingProfessionalProfilePanel.d.ts","sourceRoot":"","sources":["../src/ExistingProfessionalProfilePanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,22 @@
|
|
|
2
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 jsQrModule from 'jsqr';
|
|
5
|
+
import { ProfileIdentityTarget } from './ProfileIdentityTarget.js';
|
|
5
6
|
const decodeQrImage = jsQrModule;
|
|
6
7
|
/** Shared BFF serialization keeps professional evidence distinct from an invitation code. */
|
|
7
8
|
export function toExistingProfessionalProfileRequest(input) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
+
});
|
|
11
21
|
}
|
|
12
22
|
/**
|
|
13
23
|
* Registers this portal device for an already-active professional profile.
|
|
@@ -16,13 +26,20 @@ export function toExistingProfessionalProfileRequest(input) {
|
|
|
16
26
|
* is the preferred path; a single DID-or-URN field plus sector is the explicit
|
|
17
27
|
* manual fallback and never becomes browser-selected authority by itself.
|
|
18
28
|
*/
|
|
19
|
-
export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onSubmit }) {
|
|
29
|
+
export function ExistingProfessionalProfilePanel({ copy, sectors, countries = [], regionsByCountry = {}, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }) {
|
|
20
30
|
const [method, setMethod] = useState('credential');
|
|
21
31
|
const [professionalCredential, setProfessionalCredential] = useState('');
|
|
22
32
|
const [professionalIdentifier, setProfessionalIdentifier] = useState('');
|
|
23
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('');
|
|
24
38
|
const [pin, setPin] = useState('');
|
|
25
39
|
const [pinConfirmation, setPinConfirmation] = useState('');
|
|
40
|
+
const [resolvedProfiles, setResolvedProfiles] = useState([]);
|
|
41
|
+
const [selectedProfile, setSelectedProfile] = useState();
|
|
42
|
+
const [resolving, setResolving] = useState(false);
|
|
26
43
|
const [localError, setLocalError] = useState('');
|
|
27
44
|
const [cameraActive, setCameraActive] = useState(false);
|
|
28
45
|
const videoRef = useRef(null);
|
|
@@ -105,28 +122,73 @@ export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, o
|
|
|
105
122
|
}
|
|
106
123
|
setProfessionalCredential(credential);
|
|
107
124
|
}
|
|
108
|
-
function
|
|
125
|
+
function evidenceInput() {
|
|
126
|
+
if (method === 'credential') {
|
|
127
|
+
if (!professionalCredential) {
|
|
128
|
+
setLocalError(copy.professionalCredentialRequired);
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return { method, professionalCredential: professionalCredential.trim() };
|
|
132
|
+
}
|
|
133
|
+
if (countries.length)
|
|
134
|
+
return {
|
|
135
|
+
method: 'organization',
|
|
136
|
+
country,
|
|
137
|
+
...(regional ? { region } : {}),
|
|
138
|
+
organizationIdentifier: organizationIdentifier.trim(),
|
|
139
|
+
sector,
|
|
140
|
+
};
|
|
141
|
+
return { method, professionalIdentifier: professionalIdentifier.trim(), sector };
|
|
142
|
+
}
|
|
143
|
+
async function submit(event) {
|
|
109
144
|
event.preventDefault();
|
|
145
|
+
const evidence = evidenceInput();
|
|
146
|
+
if (!evidence)
|
|
147
|
+
return;
|
|
148
|
+
if (onResolve && resolvedProfiles.length === 0) {
|
|
149
|
+
setLocalError('');
|
|
150
|
+
setResolving(true);
|
|
151
|
+
try {
|
|
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);
|
|
156
|
+
}
|
|
157
|
+
catch (reason) {
|
|
158
|
+
setLocalError(reason instanceof Error ? reason.message : copy.verificationFailed || 'professional_profile_verification_failed');
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
setResolving(false);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
110
165
|
if (pin !== pinConfirmation) {
|
|
111
166
|
setLocalError(copy.pinMismatch);
|
|
112
167
|
return;
|
|
113
168
|
}
|
|
114
169
|
setLocalError('');
|
|
115
|
-
if (method === 'credential') {
|
|
116
|
-
|
|
117
|
-
|
|
170
|
+
if (evidence.method === 'credential') {
|
|
171
|
+
void onSubmit({ ...evidence, pin });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (evidence.method === 'organization') {
|
|
175
|
+
if (!selectedProfile?.selectionValue) {
|
|
176
|
+
setLocalError(copy.verificationFailed || 'professional_profile_selection_required');
|
|
118
177
|
return;
|
|
119
178
|
}
|
|
120
|
-
void onSubmit({
|
|
179
|
+
void onSubmit({ ...evidence, selectedProfile: selectedProfile.selectionValue, pin });
|
|
121
180
|
return;
|
|
122
181
|
}
|
|
123
|
-
void onSubmit({
|
|
182
|
+
void onSubmit({ ...evidence, pin });
|
|
124
183
|
}
|
|
125
|
-
|
|
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 })] }), _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'
|
|
126
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 => {
|
|
127
188
|
const file = event.currentTarget.files?.[0];
|
|
128
189
|
if (file)
|
|
129
190
|
void captureCredential(() => readCredentialImage(file)).catch(() => setLocalError(copy.credentialQrUnreadable));
|
|
130
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] })
|
|
131
|
-
: _jsxs(_Fragment, { children: [_jsxs("label", { children: [_jsx("span", { children: copy.
|
|
192
|
+
: _jsxs(_Fragment, { children: [countries.length ? _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 })] }) });
|
|
132
194
|
}
|
package/dist/PortalHeader.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/PortalHeader.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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';
|
|
6
6
|
import { PortalBrandArtwork } from './PortalBrandArtwork.js';
|
|
7
|
+
import { ProfileIdentityIcon } from './ProfileIdentityTarget.js';
|
|
7
8
|
import { PortalCurrentSubjectControl, PortalCurrentSubjectSlot, } from './PortalCurrentSubject.js';
|
|
8
9
|
/**
|
|
9
10
|
* Shared account/preferences header.
|
|
@@ -11,13 +12,15 @@ import { PortalCurrentSubjectControl, PortalCurrentSubjectSlot, } from './Portal
|
|
|
11
12
|
* Authentication is deliberately supplied as data and callbacks. The
|
|
12
13
|
* component never imports Firebase and never treats browser state as a role.
|
|
13
14
|
*/
|
|
14
|
-
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, }) {
|
|
15
16
|
const [open, setOpen] = useState(false);
|
|
16
17
|
const [density, setDensity] = useState('medium');
|
|
17
18
|
const [theme, setTheme] = useState('system');
|
|
18
19
|
const ref = useRef(null);
|
|
19
20
|
const densityKey = `${storageNamespace}.density`;
|
|
20
21
|
const themeKey = `${storageNamespace}.theme`;
|
|
22
|
+
const signedIn = Boolean(accountIdentifier);
|
|
23
|
+
const menuLabel = signedIn ? copy.accountAndPreferences : copy.chooseAccess;
|
|
21
24
|
useEffect(() => {
|
|
22
25
|
const savedDensity = resolvePortalUiDensity(localStorage.getItem(densityKey));
|
|
23
26
|
const savedTheme = resolvePortalUiTheme(localStorage.getItem(themeKey));
|
|
@@ -68,23 +71,23 @@ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, lan
|
|
|
68
71
|
}
|
|
69
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
|
|
70
73
|
? _jsx(PortalCurrentSubjectControl, { ...currentSubject })
|
|
71
|
-
: _jsx(PortalCurrentSubjectSlot, {}), _jsxs("div", { className: "uhcp-account-menu", ref: ref, children: [_jsx("button", { type: "button", className: "uhcp-trigger", onClick: () => setOpen((value) => !value), "aria-label":
|
|
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: () => {
|
|
72
75
|
onSelectAccount?.(option.id);
|
|
73
76
|
setOpen(false);
|
|
74
|
-
}, 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
|
|
75
|
-
?
|
|
76
|
-
|
|
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
|
|
77
83
|
? (_jsx("a", { href: action.href, onClick: () => setOpen(false), children: action.label }, action.id))
|
|
78
84
|
: (_jsx("button", { type: "button", onClick: () => {
|
|
79
85
|
setOpen(false);
|
|
80
86
|
void action.onSelect?.();
|
|
81
87
|
}, children: action.label }, action.id))), accountIdentifier && onSignOut
|
|
82
88
|
? _jsxs("button", { type: "button", onClick: () => { setOpen(false); void onSignOut(); }, children: ["\u21AA ", copy.signOut] })
|
|
83
|
-
: _jsxs("a", { href: guestActionHref, onClick: () => setOpen(false), children: [_jsx(
|
|
89
|
+
: _jsxs("a", { href: guestActionHref, onClick: () => setOpen(false), children: [_jsx(ProfileIdentityIcon, {}), " ", copy.chooseAccess] })] })] }))] })] }));
|
|
84
90
|
}
|
|
85
91
|
function PreferenceGroup({ label, children }) {
|
|
86
92
|
return _jsxs("fieldset", { className: "uhcp-preferences", children: [_jsx("legend", { children: label }), _jsx("div", { children: children })] });
|
|
87
93
|
}
|
|
88
|
-
function PersonIcon() {
|
|
89
|
-
return (_jsxs("svg", { className: "uhcp-person-icon", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z", stroke: "currentColor", strokeWidth: "1.8" }), _jsx("path", { d: "M4.5 21a7.5 7.5 0 0 1 15 0", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" })] }));
|
|
90
|
-
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProfileProtectionTargetV2 } from './contracts.js';
|
|
2
|
+
export declare function ProfileIdentityIcon(): import("react").JSX.Element;
|
|
3
|
+
/** Shared presentation for one host-resolved profile. It never selects or grants a role. */
|
|
4
|
+
export declare function ProfileIdentityTarget({ target }: Readonly<{
|
|
5
|
+
target: ProfileProtectionTargetV2;
|
|
6
|
+
}>): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=ProfileIdentityTarget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProfileIdentityTarget.d.ts","sourceRoot":"","sources":["../src/ProfileIdentityTarget.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAE/D,wBAAgB,mBAAmB,gCAKlC;AAED,4FAA4F;AAC5F,wBAAgB,qBAAqB,CAAC,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC;IAAE,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAAC,+BAkBhG"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function ProfileIdentityIcon() {
|
|
3
|
+
return _jsxs("svg", { "data-testid": "professional-identity-icon", className: "uhcp-person-icon", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z", stroke: "currentColor", strokeWidth: "1.8" }), _jsx("path", { d: "M4.5 21a7.5 7.5 0 0 1 15 0", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" })] });
|
|
4
|
+
}
|
|
5
|
+
/** Shared presentation for one host-resolved profile. It never selects or grants a role. */
|
|
6
|
+
export function ProfileIdentityTarget({ target }) {
|
|
7
|
+
return _jsxs("div", { className: "uhcp-profile-target", children: [target.icon
|
|
8
|
+
? _jsx("img", { src: target.icon.src, alt: target.icon.alt, width: target.icon.width, height: target.icon.height })
|
|
9
|
+
: _jsx("span", { className: "uhcp-profile-target-default-icon", children: _jsx(ProfileIdentityIcon, {}) }), _jsxs("div", { className: "uhcp-profile-target-content", children: [_jsxs("div", { className: "uhcp-profile-target-copy", children: [_jsx("strong", { children: target.title }), target.subtitle && _jsx("span", { children: target.subtitle })] }), target.badges?.length ? _jsx("ul", { className: "uhcp-profile-target-badges uhcp-profile-target-badges-centered", "aria-label": target.title, children: target.badges.map(badge => _jsx("li", { children: badge }, badge)) }) : null, target.details?.length ? _jsx("dl", { className: "uhcp-profile-target-details", children: target.details.map(detail => _jsxs("div", { children: [_jsx("dt", { children: detail.label }), _jsx("dd", { children: detail.value })] }, `${detail.label}:${detail.value}`)) }) : null] })] });
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileProtectionGateV2.d.ts","sourceRoot":"","sources":["../src/ProfileProtectionGateV2.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA+C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACnF,OAAO,KAAK,EACV,uBAAuB,EACvB,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"ProfileProtectionGateV2.d.ts","sourceRoot":"","sources":["../src/ProfileProtectionGateV2.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA+C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACnF,OAAO,KAAK,EACV,uBAAuB,EACvB,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,gBAAgB,CAAA;AAGvB,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,KAAK,EAAE,sBAAsB,CAAA;IAC7B,MAAM,EAAE,yBAAyB,CAAA;IACjC,IAAI,EAAE,uBAAuB,CAAA;IAC7B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAC9E,eAAe,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,iBAAiB,CAAC,EAAE,SAAS,CAAA;IAC7B,gBAAgB,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GACT,EAAE,4BAA4B,+BA0G9B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { ProfileIdentityTarget } from './ProfileIdentityTarget.js';
|
|
4
5
|
/**
|
|
5
6
|
* Presentation-only profile gate that identifies the host-authorized target.
|
|
6
7
|
*
|
|
@@ -79,5 +80,5 @@ export function ProfileProtectionGateV2({ state, target, copy, onUnlock, onPassk
|
|
|
79
80
|
: state === 'unavailable'
|
|
80
81
|
? copy.unavailableBody
|
|
81
82
|
: copy.unlockBody;
|
|
82
|
-
return (_jsxs("section", { className: "uhcp-profile-gate uhcp-profile-gate-v2", "aria-live": "polite", children: [_jsxs("div", { className: "uhcp-profile-gate-copy", children: [_jsx("h1", { children: title }), _jsx("p", { children: body })] }),
|
|
83
|
+
return (_jsxs("section", { className: "uhcp-profile-gate uhcp-profile-gate-v2", "aria-live": "polite", children: [_jsxs("div", { className: "uhcp-profile-gate-copy", children: [_jsx("h1", { children: title }), _jsx("p", { children: body })] }), _jsx(ProfileIdentityTarget, { target: target }), state === 'locked' && onPasskeyUnlock && _jsx("button", { type: "button", className: "uhcp-action-unlock", "data-portal-action": "unlock-profile-with-passkey", disabled: busy, onClick: unlockWithPasskey, children: copy.unlockWithPasskey }), state === 'locked' && _jsxs("details", { className: "uhcp-profile-pin-fallback", open: !onPasskeyUnlock, children: [_jsx("summary", { children: onPasskeyUnlock ? copy.usePinInstead : copy.pinLabel }), _jsxs("form", { onSubmit: unlock, children: [_jsxs("label", { children: [copy.pinLabel, _jsx("input", { ref: input, type: "password", inputMode: "numeric", minLength: 6, autoComplete: "current-password", value: pin, onChange: (event) => setPin(event.target.value) })] }), error && _jsx("p", { role: "alert", className: "uhcp-error", children: error }), _jsx("button", { type: "submit", className: "uhcp-action-unlock", "data-portal-action": "unlock-profile", disabled: busy, children: copy.unlock })] }), pinRejected && pinFailureActions ? _jsx("div", { className: "uhcp-profile-pin-recovery", children: pinFailureActions }) : null] }), state === 'unavailable' && onRetry && _jsx("button", { type: "button", onClick: onRetry, children: copy.retry }), secondaryActions && _jsx("div", { className: "uhcp-profile-gate-secondary", children: secondaryActions })] }));
|
|
83
84
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './PortalShell.js';
|
|
|
7
7
|
export * from './PortalEntry.js';
|
|
8
8
|
export * from './ProfileProtectionGate.js';
|
|
9
9
|
export * from './ProfileProtectionGateV2.js';
|
|
10
|
+
export * from './ProfileIdentityTarget.js';
|
|
10
11
|
export * from './PortalLoadingSkeleton.js';
|
|
11
12
|
export * from './ClinicalSectionSummary.js';
|
|
12
13
|
export * from './IpsBundleSummary.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qCAAqC,CAAA;AACnD,cAAc,mCAAmC,CAAA;AACjD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA;AACpD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uCAAuC,CAAA;AACrD,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,yCAAyC,CAAA;AACvD,cAAc,uCAAuC,CAAA;AACrD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qCAAqC,CAAA;AACnD,cAAc,mCAAmC,CAAA;AACjD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA;AACpD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uCAAuC,CAAA;AACrD,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,yCAAyC,CAAA;AACvD,cAAc,uCAAuC,CAAA;AACrD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './PortalShell.js';
|
|
|
7
7
|
export * from './PortalEntry.js';
|
|
8
8
|
export * from './ProfileProtectionGate.js';
|
|
9
9
|
export * from './ProfileProtectionGateV2.js';
|
|
10
|
+
export * from './ProfileIdentityTarget.js';
|
|
10
11
|
export * from './PortalLoadingSkeleton.js';
|
|
11
12
|
export * from './ClinicalSectionSummary.js';
|
|
12
13
|
export * from './IpsBundleSummary.js';
|
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);
|
|
@@ -192,6 +194,8 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
192
194
|
.uhcp-profile-gate-copy p { margin-top: .45rem; }
|
|
193
195
|
.uhcp-profile-target { align-items: center; background: var(--uhcp-panel); border: 1px solid color-mix(in srgb, var(--uhcp-turquoise) 32%, var(--uhcp-border)); border-radius: 1rem; display: grid; gap: 1rem; grid-template-columns: auto minmax(0, 1fr); padding: 1rem; }
|
|
194
196
|
.uhcp-profile-target > img { background: var(--uhcp-panel); border-radius: .75rem; object-fit: contain; }
|
|
197
|
+
.uhcp-profile-target-default-icon { align-items: center; background: var(--uhcp-surface-muted); border: 1px solid var(--uhcp-border); border-radius: 50%; color: var(--uhcp-turquoise); display: flex; height: 4.5rem; justify-content: center; width: 4.5rem; }
|
|
198
|
+
.uhcp-profile-target-default-icon .uhcp-person-icon { height: 2.3rem; width: 2.3rem; }
|
|
195
199
|
.uhcp-profile-target-content { display: grid; gap: .65rem; min-width: 0; }
|
|
196
200
|
.uhcp-profile-target-copy { display: grid; gap: .2rem; text-align: left; }
|
|
197
201
|
.uhcp-profile-target-copy > strong { color: var(--uhcp-text); font-size: 1.2rem; }
|
|
@@ -211,6 +215,18 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
211
215
|
.uhcp-profile-target-badges-centered { justify-content: center; }
|
|
212
216
|
}
|
|
213
217
|
.uhcp-profile-pin-fallback { border-top: 1px solid var(--uhcp-border); padding-top: .8rem; }
|
|
218
|
+
.uhcp-professional-evidence-methods { border: 0; display: grid; gap: .75rem; margin: 0; padding: 0; }
|
|
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; }
|
|
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); }
|
|
226
|
+
.uhcp-professional-pin-setup { display: grid; gap: 1rem; }
|
|
227
|
+
.uhcp-professional-pin-setup > header h1, .uhcp-professional-pin-setup > header p { margin: 0; }
|
|
228
|
+
.uhcp-professional-pin-setup > header p { margin-top: .45rem; }
|
|
229
|
+
.uhcp-professional-pin-setup .uhcp-secondary-action { background: transparent; border: 1px solid var(--uhcp-border); color: var(--uhcp-text); }
|
|
214
230
|
.uhcp-profile-pin-fallback summary { color: var(--uhcp-text); cursor: pointer; font-weight: 700; }
|
|
215
231
|
.uhcp-profile-pin-fallback form { margin-top: .8rem; }
|
|
216
232
|
.uhcp-profile-pin-recovery { display: flex; justify-content: flex-start; margin-top: .7rem; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uhc-portal-components-react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.39",
|
|
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.
|
|
37
|
+
"sos-data-utils-ts": "0.6.21",
|
|
38
38
|
"terminology-components-react": "0.2.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|