uhc-portal-components-react 0.2.35 → 0.2.37

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.37 - 2026-11-03
4
+
5
+ - Split existing-professional registration into evidence verification and
6
+ device-PIN setup so the verified role and organization are visible before a
7
+ local profile can be protected.
8
+ - Reuse one identity icon and resolved-profile target across the portal header,
9
+ profile unlock and professional registration screens.
10
+
11
+ ## 0.2.36 - 2026-11-03
12
+
13
+ - Add an accessible shared card-provider selector that returns only the opaque
14
+ provider identity selected by the user and keeps unavailable choices visible
15
+ without granting browser-side authority.
16
+
3
17
  ## 0.2.35 - 2026-11-02
4
18
 
5
19
  - Let a portal send its compact official mark to the product website while the
package/README.md CHANGED
@@ -136,8 +136,9 @@ The package deliberately exposes modules instead of a cloned application:
136
136
  BFF accepts the invitation.
137
137
  - `ExistingProfessionalProfilePanel`: existing employees scan or upload a
138
138
  signed professional-credential QR, or use the explicit DID/URN plus sector
139
- fallback. The host verifies the evidence before DCR; this screen never
140
- accepts an employee invitation.
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.
141
142
  - `SubjectAccessRequestPanel`: controlled provider-enabled QR, numeric-code,
142
143
  `did:web`, microchip, tattoo, email or telephone locator. Resolution and
143
144
  returned identifier evidence never grant subject access.
@@ -0,0 +1,19 @@
1
+ import type { SubjectCardProviderDescriptor } from 'sos-data-utils-ts/subject-card-directory';
2
+ export type CardProviderChoice = Pick<SubjectCardProviderDescriptor, 'providerId' | 'displayName'> & Readonly<{
3
+ available?: boolean;
4
+ }>;
5
+ export type CardProviderSelectorCopy = Readonly<{
6
+ legend: string;
7
+ description: string;
8
+ unavailable: string;
9
+ }>;
10
+ export type CardProviderSelectorProps = Readonly<{
11
+ providers: readonly CardProviderChoice[];
12
+ value: string;
13
+ onChange: (providerId: string) => void;
14
+ copy: CardProviderSelectorCopy;
15
+ disabled?: boolean;
16
+ }>;
17
+ /** Shared presentational selector; the host BFF remains provider authority. */
18
+ export declare function CardProviderSelector({ providers, value, onChange, copy, disabled, }: CardProviderSelectorProps): import("react").JSX.Element;
19
+ //# sourceMappingURL=CardProviderSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CardProviderSelector.d.ts","sourceRoot":"","sources":["../src/CardProviderSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,0CAA0C,CAAA;AAE7F,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,6BAA6B,EAAE,YAAY,GAAG,aAAa,CAAC,GAAG,QAAQ,CAAC;IAC5G,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAC,CAAA;AAEF,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,IAAI,EAAE,wBAAwB,CAAA;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC,CAAA;AAEF,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,QAAgB,GACjB,EAAE,yBAAyB,+BAwB3B"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Shared presentational selector; the host BFF remains provider authority. */
3
+ export function CardProviderSelector({ providers, value, onChange, copy, disabled = false, }) {
4
+ return _jsxs("fieldset", { className: "uhcp-card-provider-selector", disabled: disabled, children: [_jsx("legend", { children: copy.legend }), _jsx("p", { children: copy.description }), _jsx("div", { className: "uhcp-card-provider-options", children: providers.map(provider => {
5
+ const unavailable = provider.available === false;
6
+ return _jsxs("label", { className: "uhcp-card-provider-option", children: [_jsx("input", { type: "radio", name: "card-provider", value: provider.providerId, checked: value === provider.providerId, disabled: unavailable, onChange: () => onChange(provider.providerId) }), _jsxs("span", { children: [_jsx("strong", { children: provider.displayName }), unavailable ? _jsxs(_Fragment, { children: [" ", _jsx("small", { children: copy.unavailable })] }) : null] })] }, provider.providerId);
7
+ }) })] });
8
+ }
@@ -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;
@@ -16,6 +17,11 @@ export type ExistingProfessionalProfileCopy = Readonly<{
16
17
  pinLabel: string;
17
18
  pinConfirmationLabel: string;
18
19
  pinMismatch: string;
20
+ verify?: string;
21
+ verifying?: string;
22
+ verifiedProfile?: string;
23
+ changeEvidence?: string;
24
+ verificationFailed?: string;
19
25
  submit: string;
20
26
  submitting: string;
21
27
  }>;
@@ -29,6 +35,17 @@ export type ExistingProfessionalProfileInput = Readonly<{
29
35
  sector: string;
30
36
  pin: string;
31
37
  }>;
38
+ export type ExistingProfessionalEvidenceInput = Readonly<{
39
+ method: 'credential';
40
+ professionalCredential: string;
41
+ }> | Readonly<{
42
+ method: 'identifier';
43
+ professionalIdentifier: string;
44
+ sector: string;
45
+ }>;
46
+ export type ResolvedExistingProfessionalProfile = Omit<ProfileProtectionTargetV2, 'eyebrow'> & Readonly<{
47
+ eyebrow?: string;
48
+ }>;
32
49
  export type ExistingProfessionalProfileRequest = Readonly<{
33
50
  professionalCredential: string;
34
51
  pin: string;
@@ -50,6 +67,7 @@ export type ExistingProfessionalProfilePanelProps = Readonly<{
50
67
  error?: string;
51
68
  onScanProfessionalCredential?: () => Promise<string | undefined>;
52
69
  onReadProfessionalCredentialImage?: (file: File) => Promise<string | undefined>;
70
+ onResolve?: (input: ExistingProfessionalEvidenceInput) => Promise<ResolvedExistingProfessionalProfile>;
53
71
  onSubmit: (input: ExistingProfessionalProfileInput) => void | Promise<void>;
54
72
  }>;
55
73
  /**
@@ -59,5 +77,5 @@ export type ExistingProfessionalProfilePanelProps = Readonly<{
59
77
  * is the preferred path; a single DID-or-URN field plus sector is the explicit
60
78
  * manual fallback and never becomes browser-selected authority by itself.
61
79
  */
62
- export declare function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onSubmit }: ExistingProfessionalProfilePanelProps): import("react").JSX.Element;
80
+ export declare function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }: ExistingProfessionalProfilePanelProps): import("react").JSX.Element;
63
81
  //# sourceMappingURL=ExistingProfessionalProfilePanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExistingProfessionalProfilePanel.d.ts","sourceRoot":"","sources":["../src/ExistingProfessionalProfilePanel.tsx"],"names":[],"mappings":"AAWA,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,0BAA0B,EAAE,MAAM,CAAA;IAClC,8BAA8B,EAAE,MAAM,CAAA;IACtC,2BAA2B,EAAE,MAAM,CAAA;IACnC,8BAA8B,EAAE,MAAM,CAAA;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,sBAAsB,EAAE,MAAM,CAAA;IAC9B,2BAA2B,EAAE,MAAM,CAAA;IACnC,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,CAAC,CAAA;AAEF,MAAM,MAAM,gCAAgC,GACxC,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,sBAAsB,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC/E,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,sBAAsB,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEnG,MAAM,MAAM,kCAAkC,GAC1C,QAAQ,CAAC;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GACzD,QAAQ,CAAC;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEjF,6FAA6F;AAC7F,wBAAgB,oCAAoC,CAAC,KAAK,EAAE,gCAAgC,GAAG,kCAAkC,CAIhI;AAED,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEzF,MAAM,MAAM,qCAAqC,GAAG,QAAQ,CAAC;IAC3D,IAAI,EAAE,+BAA+B,CAAA;IACrC,OAAO,EAAE,SAAS,gCAAgC,EAAE,CAAA;IACpD,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4BAA4B,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAChE,iCAAiC,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAC/E,QAAQ,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5E,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,4BAA4B,EAAE,iCAAiC,EAAE,QAAQ,EAAE,EAAE,qCAAqC,+BAuIhM"}
1
+ {"version":3,"file":"ExistingProfessionalProfilePanel.d.ts","sourceRoot":"","sources":["../src/ExistingProfessionalProfilePanel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AAS/D,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,iBAAiB,EAAE,MAAM,CAAA;IACzB,0BAA0B,EAAE,MAAM,CAAA;IAClC,8BAA8B,EAAE,MAAM,CAAA;IACtC,2BAA2B,EAAE,MAAM,CAAA;IACnC,8BAA8B,EAAE,MAAM,CAAA;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,sBAAsB,EAAE,MAAM,CAAA;IAC9B,2BAA2B,EAAE,MAAM,CAAA;IACnC,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,CAAC,CAAA;AAEF,MAAM,MAAM,gCAAgC,GACxC,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,sBAAsB,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC/E,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,sBAAsB,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEnG,MAAM,MAAM,iCAAiC,GACzC,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,sBAAsB,EAAE,MAAM,CAAA;CAAE,CAAC,GAClE,QAAQ,CAAC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,sBAAsB,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEtF,MAAM,MAAM,mCAAmC,GAAG,IAAI,CAAC,yBAAyB,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE7H,MAAM,MAAM,kCAAkC,GAC1C,QAAQ,CAAC;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GACzD,QAAQ,CAAC;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEjF,6FAA6F;AAC7F,wBAAgB,oCAAoC,CAAC,KAAK,EAAE,gCAAgC,GAAG,kCAAkC,CAIhI;AAED,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEzF,MAAM,MAAM,qCAAqC,GAAG,QAAQ,CAAC;IAC3D,IAAI,EAAE,+BAA+B,CAAA;IACrC,OAAO,EAAE,SAAS,gCAAgC,EAAE,CAAA;IACpD,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4BAA4B,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAChE,iCAAiC,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAC/E,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,iCAAiC,KAAK,OAAO,CAAC,mCAAmC,CAAC,CAAA;IACtG,QAAQ,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5E,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,4BAA4B,EAAE,iCAAiC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,qCAAqC,+BA+K3M"}
@@ -2,6 +2,7 @@
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) {
@@ -16,13 +17,15 @@ export function toExistingProfessionalProfileRequest(input) {
16
17
  * is the preferred path; a single DID-or-URN field plus sector is the explicit
17
18
  * manual fallback and never becomes browser-selected authority by itself.
18
19
  */
19
- export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onSubmit }) {
20
+ export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onResolve, onSubmit }) {
20
21
  const [method, setMethod] = useState('credential');
21
22
  const [professionalCredential, setProfessionalCredential] = useState('');
22
23
  const [professionalIdentifier, setProfessionalIdentifier] = useState('');
23
24
  const [sector, setSector] = useState(sectors[0]?.value || '');
24
25
  const [pin, setPin] = useState('');
25
26
  const [pinConfirmation, setPinConfirmation] = useState('');
27
+ const [resolvedProfile, setResolvedProfile] = useState();
28
+ const [resolving, setResolving] = useState(false);
26
29
  const [localError, setLocalError] = useState('');
27
30
  const [cameraActive, setCameraActive] = useState(false);
28
31
  const videoRef = useRef(null);
@@ -105,28 +108,53 @@ export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, o
105
108
  }
106
109
  setProfessionalCredential(credential);
107
110
  }
108
- function submit(event) {
111
+ function evidenceInput() {
112
+ if (method === 'credential') {
113
+ if (!professionalCredential) {
114
+ setLocalError(copy.professionalCredentialRequired);
115
+ return undefined;
116
+ }
117
+ return { method, professionalCredential: professionalCredential.trim() };
118
+ }
119
+ return { method, professionalIdentifier: professionalIdentifier.trim(), sector };
120
+ }
121
+ async function submit(event) {
109
122
  event.preventDefault();
123
+ const evidence = evidenceInput();
124
+ if (!evidence)
125
+ return;
126
+ if (onResolve && !resolvedProfile) {
127
+ setLocalError('');
128
+ setResolving(true);
129
+ try {
130
+ setResolvedProfile(await onResolve(evidence));
131
+ }
132
+ catch (reason) {
133
+ setLocalError(reason instanceof Error ? reason.message : copy.verificationFailed || 'professional_profile_verification_failed');
134
+ }
135
+ finally {
136
+ setResolving(false);
137
+ }
138
+ return;
139
+ }
110
140
  if (pin !== pinConfirmation) {
111
141
  setLocalError(copy.pinMismatch);
112
142
  return;
113
143
  }
114
144
  setLocalError('');
115
- if (method === 'credential') {
116
- if (!professionalCredential) {
117
- setLocalError(copy.professionalCredentialRequired);
118
- return;
119
- }
120
- void onSubmit({ method, professionalCredential: professionalCredential.trim(), pin });
145
+ if (evidence.method === 'credential') {
146
+ void onSubmit({ ...evidence, pin });
121
147
  return;
122
148
  }
123
- void onSubmit({ method, professionalIdentifier: professionalIdentifier.trim(), sector, pin });
149
+ void onSubmit({ ...evidence, pin });
124
150
  }
125
- 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", { children: [_jsxs("label", { children: [_jsx("input", { type: "radio", name: "professionalEvidenceMethod", value: "credential", checked: method === 'credential', onChange: () => setMethod('credential') }), copy.credentialMethodLabel] }), _jsxs("label", { children: [_jsx("input", { type: "radio", name: "professionalEvidenceMethod", value: "identifier", checked: method === 'identifier', onChange: () => setMethod('identifier') }), copy.manualMethodLabel] })] }), method === 'credential'
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'
126
154
  ? _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
155
  const file = event.currentTarget.files?.[0];
128
156
  if (file)
129
157
  void captureCredential(() => readCredentialImage(file)).catch(() => setLocalError(copy.credentialQrUnreadable));
130
158
  } })] }), 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.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)) })] })] }), _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 })] }) });
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 })] }) });
132
160
  }
@@ -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;AAOvB,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,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"}
@@ -4,6 +4,7 @@ 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.
@@ -68,7 +69,7 @@ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, lan
68
69
  }
69
70
  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
71
  ? _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": copy.accountAndPreferences, "aria-expanded": open, children: _jsx(PersonIcon, {}) }), 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: () => {
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: () => {
72
73
  onSelectAccount?.(option.id);
73
74
  setOpen(false);
74
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
@@ -80,11 +81,8 @@ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, lan
80
81
  void action.onSelect?.();
81
82
  }, children: action.label }, action.id))), accountIdentifier && onSignOut
82
83
  ? _jsxs("button", { type: "button", onClick: () => { setOpen(false); void onSignOut(); }, children: ["\u21AA ", copy.signOut] })
83
- : _jsxs("a", { href: guestActionHref, onClick: () => setOpen(false), children: [_jsx(PersonIcon, {}), " ", copy.chooseAccess] })] })] }))] })] }));
84
+ : _jsxs("a", { href: guestActionHref, onClick: () => setOpen(false), children: [_jsx(ProfileIdentityIcon, {}), " ", copy.chooseAccess] })] })] }))] })] }));
84
85
  }
85
86
  function PreferenceGroup({ label, children }) {
86
87
  return _jsxs("fieldset", { className: "uhcp-preferences", children: [_jsx("legend", { children: label }), _jsx("div", { children: children })] });
87
88
  }
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;AAEvB,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,+BAwH9B"}
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 })] }), _jsxs("div", { className: "uhcp-profile-target", children: [target.icon && _jsx("img", { src: target.icon.src, alt: target.icon.alt, width: target.icon.width, height: target.icon.height }), _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] })] }), 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
+ 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';
@@ -61,4 +62,5 @@ export * from './ExistingProfessionalProfilePanel.js';
61
62
  export * from './BrowserRealtimeCallClient.js';
62
63
  export * from './BrowserRealtimeCallPanel.js';
63
64
  export * from './passkey-policy.js';
65
+ export * from './CardProviderSelector.js';
64
66
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
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';
@@ -61,3 +62,4 @@ export * from './ExistingProfessionalProfilePanel.js';
61
62
  export * from './BrowserRealtimeCallClient.js';
62
63
  export * from './BrowserRealtimeCallPanel.js';
63
64
  export * from './passkey-policy.js';
65
+ export * from './CardProviderSelector.js';
package/dist/styles.css CHANGED
@@ -192,6 +192,8 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
192
192
  .uhcp-profile-gate-copy p { margin-top: .45rem; }
193
193
  .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
194
  .uhcp-profile-target > img { background: var(--uhcp-panel); border-radius: .75rem; object-fit: contain; }
195
+ .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; }
196
+ .uhcp-profile-target-default-icon .uhcp-person-icon { height: 2.3rem; width: 2.3rem; }
195
197
  .uhcp-profile-target-content { display: grid; gap: .65rem; min-width: 0; }
196
198
  .uhcp-profile-target-copy { display: grid; gap: .2rem; text-align: left; }
197
199
  .uhcp-profile-target-copy > strong { color: var(--uhcp-text); font-size: 1.2rem; }
@@ -211,6 +213,13 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
211
213
  .uhcp-profile-target-badges-centered { justify-content: center; }
212
214
  }
213
215
  .uhcp-profile-pin-fallback { border-top: 1px solid var(--uhcp-border); padding-top: .8rem; }
216
+ .uhcp-professional-evidence-methods { border: 0; display: grid; gap: .75rem; margin: 0; padding: 0; }
217
+ .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
+ .uhcp-professional-evidence-methods input[type='radio'] { accent-color: var(--uhcp-turquoise); height: 1.1rem; margin: 0; width: 1.1rem; }
219
+ .uhcp-professional-pin-setup { display: grid; gap: 1rem; }
220
+ .uhcp-professional-pin-setup > header h1, .uhcp-professional-pin-setup > header p { margin: 0; }
221
+ .uhcp-professional-pin-setup > header p { margin-top: .45rem; }
222
+ .uhcp-professional-pin-setup .uhcp-secondary-action { background: transparent; border: 1px solid var(--uhcp-border); color: var(--uhcp-text); }
214
223
  .uhcp-profile-pin-fallback summary { color: var(--uhcp-text); cursor: pointer; font-weight: 700; }
215
224
  .uhcp-profile-pin-fallback form { margin-top: .8rem; }
216
225
  .uhcp-profile-pin-recovery { display: flex; justify-content: flex-start; margin-top: .7rem; }
@@ -866,6 +875,15 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
866
875
  .uhcp-smart-id-card-front > footer { border-top: 1px solid #dbe3eb; display: grid; gap: .12rem; padding-top: .55rem; }
867
876
  .uhcp-smart-id-card-front > footer span { color: #526174; font-size: .58rem; font-weight: 750; letter-spacing: .08em; text-transform: uppercase; }
868
877
  .uhcp-smart-id-provider-code { font-size: .82rem; font-variant-numeric: tabular-nums; font-weight: 800; letter-spacing: .04em; line-height: 1; margin: 0; white-space: nowrap; }
878
+
879
+ .uhcp-card-provider-selector { border: 0; margin: 0; padding: 0; }
880
+ .uhcp-card-provider-selector > legend { font-size: 1rem; font-weight: 800; }
881
+ .uhcp-card-provider-selector > p { color: var(--uhcp-muted, #64748b); margin: .35rem 0 .75rem; }
882
+ .uhcp-card-provider-options { display: grid; gap: .65rem; }
883
+ .uhcp-card-provider-option { align-items: center; border: 1px solid var(--uhcp-border, #cbd5e1); border-radius: .9rem; cursor: pointer; display: flex; gap: .75rem; padding: .85rem 1rem; }
884
+ .uhcp-card-provider-option:has(input:checked) { border-color: var(--uhcp-accent, #2563eb); box-shadow: 0 0 0 2px color-mix(in srgb, var(--uhcp-accent, #2563eb) 22%, transparent); }
885
+ .uhcp-card-provider-option:has(input:disabled) { cursor: not-allowed; opacity: .62; }
886
+ .uhcp-card-provider-option span { display: flex; flex: 1; justify-content: space-between; }
869
887
  .uhcp-smart-id-card-back { background: #fff; color: #0f2942; display: grid; gap: .5rem; grid-template-rows: auto 1fr; }
870
888
  .uhcp-smart-id-card-back > header { border-bottom: 2px solid #0f9ca8; padding-bottom: .45rem; }
871
889
  .uhcp-smart-id-card-back > header strong { font-size: .68rem; letter-spacing: .07em; line-height: 1.15; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
4
4
  "description": "Brand-neutral React portal screens, account preferences and protected-profile UI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",