uhc-portal-components-react 0.2.27 → 0.2.29

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,13 +1,27 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.2.29 - 2026-10-22
4
+
5
+ - Add one product-neutral WebRTC engine and call screen for audio/video start,
6
+ acceptance, local/remote media binding and hang-up. Host BFF adapters remain
7
+ responsible for subject/service authorization, ICE issuance, signalling and
8
+ durable call state.
9
+ - Keep shared light/dark theme state synchronized with the host document so
10
+ independently deployed portal shells render the same selected theme.
11
+
12
+ ## 0.2.28 - 2026-10-22
13
+
14
+ - Add one reusable existing-professional screen with two explicit alternatives:
15
+ scan or upload the signed professional-credential QR, or enter one DID/URN
16
+ plus the organization sector manually.
17
+ - Keep the employee invitation, verified professional credential and device
18
+ registration as separate artifacts and flows.
4
19
 
5
20
  ## 0.2.27 - 2026-10-22
6
21
 
7
- - Add one brand-neutral Professional profile activation screen that accepts a
8
- single organization-issued credential and a local PIN. Product BFFs resolve
9
- organization, sector, role, licence and technical identities; the browser no
10
- longer asks employees to reconstruct separate DID and URN values.
22
+ - Add one brand-neutral employee-invitation activation screen and local PIN.
23
+ Product BFFs resolve organization, sector, role, licence and technical
24
+ identities without exposing provider internals to the browser.
11
25
 
12
26
  ## 0.2.26 - 2026-10-22
13
27
 
package/README.md CHANGED
@@ -120,6 +120,10 @@ The package deliberately exposes modules instead of a cloned application:
120
120
  - `InvitationAcceptancePanel`: controlled invitation-code UI for a related
121
121
  individual member; the host refreshes the authorized-subject list after its
122
122
  BFF accepts the invitation.
123
+ - `ExistingProfessionalProfilePanel`: existing employees scan or upload a
124
+ signed professional-credential QR, or use the explicit DID/URN plus sector
125
+ fallback. The host verifies the evidence before DCR; this screen never
126
+ accepts an employee invitation.
123
127
  - `SubjectAccessRequestPanel`: controlled provider-enabled QR, numeric-code,
124
128
  `did:web`, microchip, tattoo, email or telephone locator. Resolution and
125
129
  returned identifier evidence never grant subject access.
@@ -0,0 +1,80 @@
1
+ export type RealtimeCallMedia = 'audio' | 'video';
2
+ export type RealtimeCallSignal = Readonly<{
3
+ sequence: number;
4
+ type: 'offer' | 'answer' | 'ice-candidate';
5
+ payload: string;
6
+ }>;
7
+ export type BrowserRealtimeCallSession = Readonly<{
8
+ callId: string;
9
+ localStream: MediaStream;
10
+ remoteStream: MediaStream;
11
+ end(): Promise<void>;
12
+ }>;
13
+ export type BrowserRealtimeCallTransport<Conversation> = Readonly<{
14
+ getIceServers(conversation: Conversation): Promise<readonly RTCIceServer[]>;
15
+ startCall(input: Readonly<{
16
+ conversation: Conversation;
17
+ media: readonly RealtimeCallMedia[];
18
+ }>): Promise<Readonly<{
19
+ callId: string;
20
+ }>>;
21
+ postSignal(input: Readonly<{
22
+ conversation: Conversation;
23
+ callId: string;
24
+ signal: RealtimeCallSignal;
25
+ }>): Promise<void>;
26
+ listSignals(input: Readonly<{
27
+ conversation: Conversation;
28
+ callId: string;
29
+ afterSequence: number;
30
+ }>): Promise<readonly RealtimeCallSignal[]>;
31
+ endCall(input: Readonly<{
32
+ conversation: Conversation;
33
+ callId: string;
34
+ }>): Promise<void>;
35
+ }>;
36
+ export type BrowserRealtimeCallDependencies<Conversation> = Readonly<{
37
+ transport: BrowserRealtimeCallTransport<Conversation>;
38
+ getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
39
+ createPeerConnection?: (configuration: RTCConfiguration) => RTCPeerConnection;
40
+ createMediaStream?: () => MediaStream;
41
+ schedulePoll?: (callback: () => void, delayMs: number) => unknown;
42
+ }>;
43
+ /**
44
+ * Product-neutral WebRTC browser engine. Product BFF adapters own call
45
+ * authorization, ICE issuance and opaque signaling persistence; this engine
46
+ * never receives a private destination or sends media through the BFF.
47
+ */
48
+ export declare function createBrowserRealtimeCallClient<Conversation>(dependencies: BrowserRealtimeCallDependencies<Conversation>): {
49
+ start(input: Readonly<{
50
+ conversation: Conversation;
51
+ media: readonly RealtimeCallMedia[];
52
+ }>): Promise<Readonly<{
53
+ callId: string;
54
+ localStream: MediaStream;
55
+ remoteStream: MediaStream;
56
+ end(): Promise<void>;
57
+ }>>;
58
+ accept(input: Readonly<{
59
+ conversation: Conversation;
60
+ callId: string;
61
+ media: readonly RealtimeCallMedia[];
62
+ }>): Promise<Readonly<{
63
+ callId: string;
64
+ localStream: MediaStream;
65
+ remoteStream: MediaStream;
66
+ end(): Promise<void>;
67
+ }>>;
68
+ join(input: Readonly<{
69
+ conversation: Conversation;
70
+ callId: string;
71
+ media: readonly RealtimeCallMedia[];
72
+ initiator: boolean;
73
+ }>): Promise<Readonly<{
74
+ callId: string;
75
+ localStream: MediaStream;
76
+ remoteStream: MediaStream;
77
+ end(): Promise<void>;
78
+ }>>;
79
+ };
80
+ //# sourceMappingURL=BrowserRealtimeCallClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserRealtimeCallClient.d.ts","sourceRoot":"","sources":["../src/BrowserRealtimeCallClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Product-neutral WebRTC browser engine. Product BFF adapters own call
3
+ * authorization, ICE issuance and opaque signaling persistence; this engine
4
+ * never receives a private destination or sends media through the BFF.
5
+ */
6
+ export function createBrowserRealtimeCallClient(dependencies) {
7
+ const getUserMedia = dependencies.getUserMedia ?? (constraints => navigator.mediaDevices.getUserMedia(constraints));
8
+ const createPeerConnection = dependencies.createPeerConnection ?? (configuration => new RTCPeerConnection(configuration));
9
+ const createMediaStream = dependencies.createMediaStream ?? (() => new MediaStream());
10
+ const schedulePoll = dependencies.schedulePoll ?? ((callback, delayMs) => globalThis.setTimeout(callback, delayMs));
11
+ async function connect(input) {
12
+ const iceServers = await dependencies.transport.getIceServers(input.conversation);
13
+ const peer = createPeerConnection({ iceServers: [...iceServers] });
14
+ const localStream = await getUserMedia({ audio: input.media.includes('audio'), video: input.media.includes('video') });
15
+ const remoteStream = createMediaStream();
16
+ for (const track of localStream.getTracks())
17
+ peer.addTrack(track, localStream);
18
+ let nextSequence = 1;
19
+ let lastReadSequence = 0;
20
+ let ended = false;
21
+ peer.ontrack = event => {
22
+ for (const track of event.streams[0]?.getTracks() ?? [event.track])
23
+ remoteStream.addTrack(track);
24
+ };
25
+ peer.onicecandidate = event => {
26
+ if (!event.candidate || ended)
27
+ return;
28
+ void dependencies.transport.postSignal({
29
+ conversation: input.conversation,
30
+ callId: input.callId,
31
+ signal: { sequence: nextSequence++, type: 'ice-candidate', payload: JSON.stringify(event.candidate.toJSON()) },
32
+ });
33
+ };
34
+ async function applySignals() {
35
+ const signals = await dependencies.transport.listSignals({
36
+ conversation: input.conversation, callId: input.callId, afterSequence: lastReadSequence,
37
+ });
38
+ for (const signal of signals) {
39
+ lastReadSequence = Math.max(lastReadSequence, signal.sequence);
40
+ nextSequence = Math.max(nextSequence, signal.sequence + 1);
41
+ if (signal.type === 'ice-candidate')
42
+ await peer.addIceCandidate(JSON.parse(signal.payload));
43
+ else if (signal.type === 'offer' && !input.initiator) {
44
+ await peer.setRemoteDescription(JSON.parse(signal.payload));
45
+ const answer = await peer.createAnswer();
46
+ await peer.setLocalDescription(answer);
47
+ await dependencies.transport.postSignal({
48
+ conversation: input.conversation, callId: input.callId,
49
+ signal: { sequence: nextSequence++, type: 'answer', payload: JSON.stringify(answer) },
50
+ });
51
+ }
52
+ else if (signal.type === 'answer' && input.initiator) {
53
+ await peer.setRemoteDescription(JSON.parse(signal.payload));
54
+ }
55
+ }
56
+ }
57
+ if (input.initiator) {
58
+ const offer = await peer.createOffer();
59
+ await peer.setLocalDescription(offer);
60
+ await dependencies.transport.postSignal({
61
+ conversation: input.conversation, callId: input.callId,
62
+ signal: { sequence: nextSequence++, type: 'offer', payload: JSON.stringify(offer) },
63
+ });
64
+ }
65
+ else
66
+ await applySignals();
67
+ const poll = async () => {
68
+ if (ended)
69
+ return;
70
+ try {
71
+ await applySignals();
72
+ }
73
+ finally {
74
+ if (!ended)
75
+ schedulePoll(() => void poll(), 750);
76
+ }
77
+ };
78
+ schedulePoll(() => void poll(), 250);
79
+ return {
80
+ callId: input.callId,
81
+ localStream,
82
+ remoteStream,
83
+ async end() {
84
+ ended = true;
85
+ for (const track of localStream.getTracks())
86
+ track.stop();
87
+ peer.close();
88
+ await dependencies.transport.endCall({ conversation: input.conversation, callId: input.callId });
89
+ },
90
+ };
91
+ }
92
+ return {
93
+ async start(input) {
94
+ const { callId } = await dependencies.transport.startCall(input);
95
+ return connect({ ...input, callId, initiator: true });
96
+ },
97
+ accept(input) {
98
+ return connect({ ...input, initiator: false });
99
+ },
100
+ join(input) {
101
+ return connect(input);
102
+ },
103
+ };
104
+ }
@@ -0,0 +1,32 @@
1
+ import type { BrowserRealtimeCallSession, RealtimeCallMedia } from './BrowserRealtimeCallClient.js';
2
+ export type BrowserRealtimeCallPanelClient<Conversation> = Readonly<{
3
+ start(input: Readonly<{
4
+ conversation: Conversation;
5
+ media: readonly RealtimeCallMedia[];
6
+ }>): Promise<BrowserRealtimeCallSession>;
7
+ accept(input: Readonly<{
8
+ conversation: Conversation;
9
+ callId: string;
10
+ media: readonly RealtimeCallMedia[];
11
+ }>): Promise<BrowserRealtimeCallSession>;
12
+ }>;
13
+ export type BrowserRealtimeCallPanelLabels = Readonly<{
14
+ title: string;
15
+ start: string;
16
+ accept: string;
17
+ end: string;
18
+ connecting?: string;
19
+ connected?: string;
20
+ ended?: string;
21
+ failed?: string;
22
+ }>;
23
+ /** Product-neutral, two-sided browser call screen; authority remains in the supplied BFF client. */
24
+ export declare function BrowserRealtimeCallPanel<Conversation>({ conversation, client, incomingCallId, labels, media, onFailure, }: Readonly<{
25
+ conversation: Conversation;
26
+ client: BrowserRealtimeCallPanelClient<Conversation>;
27
+ incomingCallId?: string;
28
+ labels: BrowserRealtimeCallPanelLabels;
29
+ media?: readonly RealtimeCallMedia[];
30
+ onFailure?: (error: unknown) => void;
31
+ }>): import("react").JSX.Element;
32
+ //# sourceMappingURL=BrowserRealtimeCallPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserRealtimeCallPanel.d.ts","sourceRoot":"","sources":["../src/BrowserRealtimeCallPanel.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEnG,MAAM,MAAM,8BAA8B,CAAC,YAAY,IAAI,QAAQ,CAAC;IAClE,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAA;IAChI,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAA;CAClJ,CAAC,CAAA;AAEF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAC,CAAA;AAEF,oGAAoG;AACpG,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,EACrD,YAAY,EACZ,MAAM,EACN,cAAc,EACd,MAAM,EACN,KAA0B,EAC1B,SAAS,GACV,EAAE,QAAQ,CAAC;IACV,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,EAAE,8BAA8B,CAAC,YAAY,CAAC,CAAA;IACpD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,8BAA8B,CAAA;IACtC,KAAK,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAA;IACpC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACrC,CAAC,+BAiDD"}
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ /** Product-neutral, two-sided browser call screen; authority remains in the supplied BFF client. */
5
+ export function BrowserRealtimeCallPanel({ conversation, client, incomingCallId, labels, media = ['audio', 'video'], onFailure, }) {
6
+ const [session, setSession] = useState();
7
+ const [status, setStatus] = useState('idle');
8
+ const localVideo = useRef(null);
9
+ const remoteVideo = useRef(null);
10
+ const activeSession = useRef(undefined);
11
+ useEffect(() => {
12
+ activeSession.current = session;
13
+ if (localVideo.current)
14
+ localVideo.current.srcObject = session?.localStream ?? null;
15
+ if (remoteVideo.current)
16
+ remoteVideo.current.srcObject = session?.remoteStream ?? null;
17
+ }, [session]);
18
+ useEffect(() => () => {
19
+ if (activeSession.current)
20
+ void activeSession.current.end().catch(() => undefined);
21
+ }, []);
22
+ async function open() {
23
+ setStatus('connecting');
24
+ try {
25
+ setSession(incomingCallId
26
+ ? await client.accept({ conversation, callId: incomingCallId, media })
27
+ : await client.start({ conversation, media }));
28
+ setStatus('connected');
29
+ }
30
+ catch (error) {
31
+ onFailure?.(error);
32
+ setStatus('failed');
33
+ }
34
+ }
35
+ async function end() {
36
+ const active = session;
37
+ setSession(undefined);
38
+ setStatus('ended');
39
+ await active?.end();
40
+ }
41
+ return _jsxs("section", { className: "uhcp-realtime-call-panel", "aria-label": labels.title, children: [_jsx("h3", { children: labels.title }), !session ? _jsx("button", { type: "button", onClick: () => void open(), disabled: status === 'connecting', children: incomingCallId ? labels.accept : labels.start }) : null, _jsx("p", { role: "status", children: labels[status] || status }), session ? _jsxs("div", { className: "uhcp-realtime-call-media", children: [_jsx("video", { ref: localVideo, "data-testid": "realtime-call-local-video", autoPlay: true, muted: true, playsInline: true }), _jsx("video", { ref: remoteVideo, "data-testid": "realtime-call-remote-video", autoPlay: true, playsInline: true }), _jsx("button", { type: "button", onClick: () => void end(), children: labels.end })] }) : null] });
42
+ }
@@ -0,0 +1,63 @@
1
+ export type ExistingProfessionalProfileCopy = Readonly<{
2
+ eyebrow?: string;
3
+ title: string;
4
+ description: string;
5
+ credentialMethodLabel: string;
6
+ manualMethodLabel: string;
7
+ scanProfessionalCredential: string;
8
+ uploadProfessionalCredentialQr: string;
9
+ professionalCredentialReady: string;
10
+ professionalCredentialRequired: string;
11
+ closeCamera: string;
12
+ cameraActive: string;
13
+ credentialQrUnreadable: string;
14
+ professionalIdentifierLabel: string;
15
+ sectorLabel: string;
16
+ pinLabel: string;
17
+ pinConfirmationLabel: string;
18
+ pinMismatch: string;
19
+ submit: string;
20
+ submitting: string;
21
+ }>;
22
+ export type ExistingProfessionalProfileInput = Readonly<{
23
+ method: 'credential';
24
+ professionalCredential: string;
25
+ pin: string;
26
+ }> | Readonly<{
27
+ method: 'identifier';
28
+ professionalIdentifier: string;
29
+ sector: string;
30
+ pin: string;
31
+ }>;
32
+ export type ExistingProfessionalProfileRequest = Readonly<{
33
+ professionalCredential: string;
34
+ pin: string;
35
+ }> | Readonly<{
36
+ professionalIdentifier: string;
37
+ sectorHint: string;
38
+ pin: string;
39
+ }>;
40
+ /** Shared BFF serialization keeps professional evidence distinct from an invitation code. */
41
+ export declare function toExistingProfessionalProfileRequest(input: ExistingProfessionalProfileInput): ExistingProfessionalProfileRequest;
42
+ export type ExistingProfessionalSectorOption = Readonly<{
43
+ value: string;
44
+ label: string;
45
+ }>;
46
+ export type ExistingProfessionalProfilePanelProps = Readonly<{
47
+ copy: ExistingProfessionalProfileCopy;
48
+ sectors: readonly ExistingProfessionalSectorOption[];
49
+ busy: boolean;
50
+ error?: string;
51
+ onScanProfessionalCredential?: () => Promise<string | undefined>;
52
+ onReadProfessionalCredentialImage?: (file: File) => Promise<string | undefined>;
53
+ onSubmit: (input: ExistingProfessionalProfileInput) => void | Promise<void>;
54
+ }>;
55
+ /**
56
+ * Registers this portal device for an already-active professional profile.
57
+ * Professional evidence, employee invitations and device registration are
58
+ * separate artifacts. This screen accepts no invitation. A signed credential
59
+ * is the preferred path; a single DID-or-URN field plus sector is the explicit
60
+ * manual fallback and never becomes browser-selected authority by itself.
61
+ */
62
+ export declare function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onSubmit }: ExistingProfessionalProfilePanelProps): import("react").JSX.Element;
63
+ //# sourceMappingURL=ExistingProfessionalProfilePanel.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import jsQrModule from 'jsqr';
5
+ const decodeQrImage = jsQrModule;
6
+ /** Shared BFF serialization keeps professional evidence distinct from an invitation code. */
7
+ export function toExistingProfessionalProfileRequest(input) {
8
+ return input.method === 'credential'
9
+ ? Object.freeze({ professionalCredential: input.professionalCredential, pin: input.pin })
10
+ : Object.freeze({ professionalIdentifier: input.professionalIdentifier, sectorHint: input.sector, pin: input.pin });
11
+ }
12
+ /**
13
+ * Registers this portal device for an already-active professional profile.
14
+ * Professional evidence, employee invitations and device registration are
15
+ * separate artifacts. This screen accepts no invitation. A signed credential
16
+ * is the preferred path; a single DID-or-URN field plus sector is the explicit
17
+ * manual fallback and never becomes browser-selected authority by itself.
18
+ */
19
+ export function ExistingProfessionalProfilePanel({ copy, sectors, busy, error, onScanProfessionalCredential, onReadProfessionalCredentialImage, onSubmit }) {
20
+ const [method, setMethod] = useState('credential');
21
+ const [professionalCredential, setProfessionalCredential] = useState('');
22
+ const [professionalIdentifier, setProfessionalIdentifier] = useState('');
23
+ const [sector, setSector] = useState(sectors[0]?.value || '');
24
+ const [pin, setPin] = useState('');
25
+ const [pinConfirmation, setPinConfirmation] = useState('');
26
+ const [localError, setLocalError] = useState('');
27
+ const [cameraActive, setCameraActive] = useState(false);
28
+ const videoRef = useRef(null);
29
+ const canvasRef = useRef(null);
30
+ const streamRef = useRef(null);
31
+ const timerRef = useRef(null);
32
+ const scanResolverRef = useRef(null);
33
+ function stopCamera(value) {
34
+ if (timerRef.current)
35
+ window.clearInterval(timerRef.current);
36
+ timerRef.current = null;
37
+ streamRef.current?.getTracks().forEach(track => track.stop());
38
+ streamRef.current = null;
39
+ if (videoRef.current)
40
+ videoRef.current.srcObject = null;
41
+ setCameraActive(false);
42
+ scanResolverRef.current?.(value);
43
+ scanResolverRef.current = null;
44
+ }
45
+ useEffect(() => () => stopCamera(), []);
46
+ async function scanWithCamera() {
47
+ if (onScanProfessionalCredential)
48
+ return onScanProfessionalCredential();
49
+ const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } }, audio: false });
50
+ streamRef.current = stream;
51
+ if (!videoRef.current) {
52
+ stream.getTracks().forEach(track => track.stop());
53
+ return undefined;
54
+ }
55
+ videoRef.current.srcObject = stream;
56
+ await videoRef.current.play();
57
+ setCameraActive(true);
58
+ return new Promise(resolve => {
59
+ scanResolverRef.current = resolve;
60
+ const Detector = window.BarcodeDetector;
61
+ timerRef.current = window.setInterval(() => {
62
+ const video = videoRef.current;
63
+ if (!video)
64
+ return;
65
+ const accept = (value) => { if (value)
66
+ stopCamera(value); };
67
+ if (Detector) {
68
+ void new Detector({ formats: ['qr_code'] }).detect(video).then(([barcode]) => accept(barcode?.rawValue)).catch(() => undefined);
69
+ return;
70
+ }
71
+ const canvas = canvasRef.current;
72
+ if (!canvas || !video.videoWidth || !video.videoHeight)
73
+ return;
74
+ canvas.width = video.videoWidth;
75
+ canvas.height = video.videoHeight;
76
+ const context = canvas.getContext('2d', { willReadFrequently: true });
77
+ if (!context)
78
+ return;
79
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
80
+ accept(decodeQrImage(context.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height)?.data);
81
+ }, 400);
82
+ });
83
+ }
84
+ async function readCredentialImage(file) {
85
+ if (onReadProfessionalCredentialImage)
86
+ return onReadProfessionalCredentialImage(file);
87
+ const bitmap = await createImageBitmap(file);
88
+ const canvas = canvasRef.current;
89
+ if (!canvas)
90
+ return undefined;
91
+ canvas.width = bitmap.width;
92
+ canvas.height = bitmap.height;
93
+ const context = canvas.getContext('2d', { willReadFrequently: true });
94
+ if (!context)
95
+ return undefined;
96
+ context.drawImage(bitmap, 0, 0);
97
+ return decodeQrImage(context.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height)?.data;
98
+ }
99
+ async function captureCredential(read) {
100
+ setLocalError('');
101
+ const credential = String(await read() || '').trim();
102
+ if (!credential) {
103
+ setLocalError(copy.professionalCredentialRequired);
104
+ return;
105
+ }
106
+ setProfessionalCredential(credential);
107
+ }
108
+ function submit(event) {
109
+ event.preventDefault();
110
+ if (pin !== pinConfirmation) {
111
+ setLocalError(copy.pinMismatch);
112
+ return;
113
+ }
114
+ setLocalError('');
115
+ if (method === 'credential') {
116
+ if (!professionalCredential) {
117
+ setLocalError(copy.professionalCredentialRequired);
118
+ return;
119
+ }
120
+ void onSubmit({ method, professionalCredential: professionalCredential.trim(), pin });
121
+ return;
122
+ }
123
+ void onSubmit({ method, professionalIdentifier: professionalIdentifier.trim(), sector, pin });
124
+ }
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'
126
+ ? _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
+ const file = event.currentTarget.files?.[0];
128
+ if (file)
129
+ void captureCredential(() => readCredentialImage(file)).catch(() => setLocalError(copy.credentialQrUnreadable));
130
+ } })] }), 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 })] }) });
132
+ }
@@ -1,4 +1,4 @@
1
- import { type PortalBrand, type PortalAccountOption, type PortalLanguageOption, type PortalMenuAction, type PortalShellCopy } from './contracts.js';
1
+ import { type PortalBrand, type PortalAccountOption, type PortalLanguageOption, type PortalMenuAction, type PortalShellCopy, type PortalUiTheme } from './contracts.js';
2
2
  import { type PortalCurrentSubjectRegistration } from './PortalCurrentSubject.js';
3
3
  export type PortalHeaderProps = Readonly<{
4
4
  brand: PortalBrand;
@@ -11,6 +11,7 @@ export type PortalHeaderProps = Readonly<{
11
11
  onSelectAccount?: (id: string) => void;
12
12
  menuActions?: readonly PortalMenuAction[];
13
13
  onSignOut?: () => void | Promise<void>;
14
+ onThemeChange?: (theme: PortalUiTheme) => void;
14
15
  storageNamespace?: string;
15
16
  currentSubject?: PortalCurrentSubjectRegistration;
16
17
  }>;
@@ -20,5 +21,5 @@ export type PortalHeaderProps = Readonly<{
20
21
  * Authentication is deliberately supplied as data and callbacks. The
21
22
  * component never imports Firebase and never treats browser state as a role.
22
23
  */
23
- export declare function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions, guestActionHref, accountOptions, onSelectAccount, menuActions, onSignOut, storageNamespace, currentSubject, }: PortalHeaderProps): import("react").JSX.Element;
24
+ export declare function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions, guestActionHref, accountOptions, onSelectAccount, menuActions, onSignOut, onThemeChange, storageNamespace, currentSubject, }: PortalHeaderProps): import("react").JSX.Element;
24
25
  //# 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,EAGrB,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,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,gBAA+B,EAC/B,cAAc,GACf,EAAE,iBAAiB,+BA6KnB"}
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,+BAgLnB"}
@@ -11,7 +11,7 @@ import { PortalCurrentSubjectControl, PortalCurrentSubjectSlot, } from './Portal
11
11
  * Authentication is deliberately supplied as data and callbacks. The
12
12
  * component never imports Firebase and never treats browser state as a role.
13
13
  */
14
- export function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions = [], guestActionHref, accountOptions = [], onSelectAccount, menuActions = [], onSignOut, storageNamespace = 'uhc-portal', currentSubject, }) {
14
+ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, languageOptions = [], guestActionHref, accountOptions = [], onSelectAccount, menuActions = [], onSignOut, onThemeChange, storageNamespace = 'uhc-portal', currentSubject, }) {
15
15
  const [open, setOpen] = useState(false);
16
16
  const [density, setDensity] = useState('medium');
17
17
  const [theme, setTheme] = useState('system');
@@ -32,6 +32,8 @@ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, lan
32
32
  const apply = () => {
33
33
  const rendered = resolveRenderedPortalTheme(theme, media?.matches === true);
34
34
  document.documentElement.dataset.theme = rendered;
35
+ document.documentElement.classList.toggle('dark', rendered === 'dark');
36
+ document.documentElement.classList.toggle('light', rendered === 'light');
35
37
  document.documentElement.style.colorScheme = rendered;
36
38
  };
37
39
  apply();
@@ -62,6 +64,7 @@ export function PortalHeader({ brand, copy, accountIdentifier, languageHref, lan
62
64
  function changeTheme(value) {
63
65
  setTheme(value);
64
66
  localStorage.setItem(themeKey, value);
67
+ onThemeChange?.(value);
65
68
  }
66
69
  return (_jsxs("header", { className: "uhcp-header", style: brand.variables, children: [_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
67
70
  ? _jsx(PortalCurrentSubjectControl, { ...currentSubject })
@@ -20,9 +20,10 @@ export type ProfessionalProfileActivationPanelProps = Readonly<{
20
20
  onSubmit: (input: ProfessionalProfileActivationInput) => void | Promise<void>;
21
21
  }>;
22
22
  /**
23
- * Brand-neutral first-device activation for an authorized employee.
24
- * The BFF resolves trusted routing and authority from one organization-issued
25
- * value; the browser never reconstructs a sector, organization, DID and URN.
23
+ * Legacy brand-neutral employee-invitation activation.
24
+ * `credential` is retained as an API-compatible field name, but its value is
25
+ * an invitation/activation code, never a reusable professional credential.
26
+ * Existing professionals use ExistingProfessionalProfilePanel instead.
26
27
  */
27
28
  export declare function ProfessionalProfileActivationPanel({ copy, busy, error, onSubmit }: ProfessionalProfileActivationPanelProps): import("react").JSX.Element;
28
29
  //# sourceMappingURL=ProfessionalProfileActivationPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProfessionalProfileActivationPanel.d.ts","sourceRoot":"","sources":["../src/ProfessionalProfileActivationPanel.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,iCAAiC,GAAG,QAAQ,CAAC;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,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,kCAAkC,GAAG,QAAQ,CAAC;IACxD,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAC,CAAA;AAEF,MAAM,MAAM,uCAAuC,GAAG,QAAQ,CAAC;IAC7D,IAAI,EAAE,iCAAiC,CAAA;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,kCAAkC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9E,CAAC,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uCAAuC,+BA8B1H"}
1
+ {"version":3,"file":"ProfessionalProfileActivationPanel.d.ts","sourceRoot":"","sources":["../src/ProfessionalProfileActivationPanel.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,iCAAiC,GAAG,QAAQ,CAAC;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,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,kCAAkC,GAAG,QAAQ,CAAC;IACxD,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAC,CAAA;AAEF,MAAM,MAAM,uCAAuC,GAAG,QAAQ,CAAC;IAC7D,IAAI,EAAE,iCAAiC,CAAA;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,kCAAkC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9E,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uCAAuC,+BA8B1H"}
@@ -2,9 +2,10 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from 'react';
4
4
  /**
5
- * Brand-neutral first-device activation for an authorized employee.
6
- * The BFF resolves trusted routing and authority from one organization-issued
7
- * value; the browser never reconstructs a sector, organization, DID and URN.
5
+ * Legacy brand-neutral employee-invitation activation.
6
+ * `credential` is retained as an API-compatible field name, but its value is
7
+ * an invitation/activation code, never a reusable professional credential.
8
+ * Existing professionals use ExistingProfessionalProfilePanel instead.
8
9
  */
9
10
  export function ProfessionalProfileActivationPanel({ copy, busy, error, onSubmit }) {
10
11
  const [credential, setCredential] = useState('');
package/dist/index.d.ts CHANGED
@@ -56,5 +56,8 @@ export * from './CollapsiblePortalSection.js';
56
56
  export * from './ElapsedOperationStatus.js';
57
57
  export * from './ProfileRecoveryPanel.js';
58
58
  export * from './ProfessionalProfileActivationPanel.js';
59
+ export * from './ExistingProfessionalProfilePanel.js';
60
+ export * from './BrowserRealtimeCallClient.js';
61
+ export * from './BrowserRealtimeCallPanel.js';
59
62
  export * from './passkey-policy.js';
60
63
  //# 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,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,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,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,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"}
package/dist/index.js CHANGED
@@ -56,4 +56,7 @@ export * from './CollapsiblePortalSection.js';
56
56
  export * from './ElapsedOperationStatus.js';
57
57
  export * from './ProfileRecoveryPanel.js';
58
58
  export * from './ProfessionalProfileActivationPanel.js';
59
+ export * from './ExistingProfessionalProfilePanel.js';
60
+ export * from './BrowserRealtimeCallClient.js';
61
+ export * from './BrowserRealtimeCallPanel.js';
59
62
  export * from './passkey-policy.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
4
4
  "description": "Brand-neutral React portal screens, account preferences and protected-profile UI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -33,6 +33,7 @@
33
33
  "dependencies": {
34
34
  "fhir-data-utils-ts": "0.6.21",
35
35
  "gdc-common-utils-ts": "2.9.30",
36
+ "jsqr": "1.4.0",
36
37
  "sos-data-utils-ts": "0.6.15",
37
38
  "terminology-components-react": "0.2.0"
38
39
  },