uhc-portal-components-react 0.1.5 → 0.1.7

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.7 - 2026-07-30
4
+
5
+ - Add controlled, actor-neutral invitation-acceptance and subject-access
6
+ request panels. The same scanner/manual locator can be hosted by an
7
+ individual-member or professional facade; authorization remains in the BFF
8
+ and GW.
9
+ - Keep requested clinical sections as explicit `system|code` values supplied
10
+ by the host instead of deriving role or permission semantics in React.
11
+
12
+ ## 0.1.6 - 2026-07-30
13
+
14
+ - Consume Common Utils 2.3.12 so the shared IPS viewer uses the complete
15
+ all-section claims and localized clinical-name contract.
16
+ - Keep the official 16-section fixture and every declared IPS resource type as
17
+ the executable rendering contract consumed by portal Playwright flows.
18
+
3
19
  ## 0.1.5 - 2026-07-29
4
20
 
5
21
  - Allow advanced hosts to reuse the shared clinical card body while retaining
package/README.md CHANGED
@@ -53,6 +53,12 @@ The package deliberately exposes modules instead of a cloned application:
53
53
  - `IpsBundleSummary`: receives one in-memory IPS document Bundle, obtains all
54
54
  `Composition.section` entries through Common Utils and renders every
55
55
  section's cards. It performs no ingestion or GW call.
56
+ - `InvitationAcceptancePanel`: controlled invitation-code UI for a related
57
+ individual member; the host refreshes the authorized-subject list after its
58
+ BFF accepts the invitation.
59
+ - `SubjectAccessRequestPanel`: controlled QR, numeric-code or `did:web`
60
+ resolver plus section selector. It deliberately has no scanner role: the
61
+ same component is reusable by an individual member or a professional.
56
62
 
57
63
  Clinical label semantics stay in `gdc-common-utils-ts` / the SDK reader. Hosts
58
64
  pass human titles to `ClinicalSectionSummary`: local `CodeableConcept.text`
@@ -73,6 +79,13 @@ The browser may render an optimistic in-memory Bundle while its authenticated
73
79
  BFF persists the command. Only backend code calls
74
80
  `ingestCommunicationAndUpdateIndex(...)`; the component never does.
75
81
 
82
+ Access panels likewise contain no consent or SMART-token logic. A host first
83
+ resolves the card locator, then asks its authenticated BFF to record the access
84
+ request. If consent is already present, the BFF may obtain a narrowed SMART
85
+ token immediately; otherwise it records a `Communication` request for the
86
+ individual controller to review. The GW remains authoritative for the exact
87
+ intersection of requested and consented sections.
88
+
76
89
  UHC Personal and VetChain Personal must compose the same personal route
77
90
  modules. UHC Professional and VetChain Professional must compose the same
78
91
  professional route modules. A brand adapter may change only tokens, logo,
@@ -0,0 +1,25 @@
1
+ export type InvitationAcceptanceStatus = 'idle' | 'submitting' | 'accepted' | 'error';
2
+ export type InvitationAcceptancePanelCopy = Readonly<{
3
+ title: string;
4
+ description: string;
5
+ codeLabel: string;
6
+ submit: string;
7
+ submitting: string;
8
+ accepted: string;
9
+ }>;
10
+ export type InvitationAcceptancePanelProps = Readonly<{
11
+ copy: InvitationAcceptancePanelCopy;
12
+ code: string;
13
+ status: InvitationAcceptanceStatus;
14
+ error?: string;
15
+ onCodeChange: (code: string) => void;
16
+ onAccept: () => void | Promise<void>;
17
+ }>;
18
+ /**
19
+ * Controlled invitation-acceptance form reusable by any personal portal.
20
+ *
21
+ * The component owns no authorization semantics. The host BFF verifies the
22
+ * authenticated actor, accepts the invitation and refreshes related subjects.
23
+ */
24
+ export declare function InvitationAcceptancePanel({ copy, code, status, error, onCodeChange, onAccept, }: InvitationAcceptancePanelProps): import("react").JSX.Element;
25
+ //# sourceMappingURL=InvitationAcceptancePanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InvitationAcceptancePanel.d.ts","sourceRoot":"","sources":["../src/InvitationAcceptancePanel.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,0BAA0B,GAClC,MAAM,GACN,YAAY,GACZ,UAAU,GACV,OAAO,CAAA;AAEX,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAC,CAAA;AAEF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,IAAI,EAAE,6BAA6B,CAAA;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,0BAA0B,CAAA;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,KAAK,EACL,YAAY,EACZ,QAAQ,GACT,EAAE,8BAA8B,+BA+BhC"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Controlled invitation-acceptance form reusable by any personal portal.
4
+ *
5
+ * The component owns no authorization semantics. The host BFF verifies the
6
+ * authenticated actor, accepts the invitation and refreshes related subjects.
7
+ */
8
+ export function InvitationAcceptancePanel({ copy, code, status, error, onCodeChange, onAccept, }) {
9
+ function submit(event) {
10
+ event.preventDefault();
11
+ void onAccept();
12
+ }
13
+ return (_jsxs("section", { className: "uhcp-access-panel", "aria-labelledby": "uhcp-invitation-title", children: [_jsx("h2", { id: "uhcp-invitation-title", children: copy.title }), _jsx("p", { children: copy.description }), status === 'accepted'
14
+ ? _jsx("p", { role: "status", children: copy.accepted })
15
+ : (_jsxs("form", { onSubmit: submit, children: [_jsxs("label", { children: [_jsx("span", { children: copy.codeLabel }), _jsx("input", { value: code, onChange: (event) => onCodeChange(event.currentTarget.value), required: true, disabled: status === 'submitting' })] }), error && _jsx("p", { role: "alert", children: error }), _jsx("button", { type: "submit", disabled: status === 'submitting' || !code.trim(), children: status === 'submitting' ? copy.submitting : copy.submit })] }))] }));
16
+ }
@@ -0,0 +1,48 @@
1
+ export type SubjectLocatorKind = 'qr' | 'numeric-code' | 'did-web';
2
+ export type SubjectAccessRequestStatus = 'idle' | 'resolving' | 'resolved' | 'requesting' | 'requested' | 'granted' | 'error';
3
+ export type SubjectAccessSectionOption = Readonly<{
4
+ code: string;
5
+ label: string;
6
+ selected: boolean;
7
+ }>;
8
+ export type ResolvedSubjectAccessTarget = Readonly<{
9
+ subjectId: string;
10
+ display: string;
11
+ }>;
12
+ export type SubjectAccessRequestPanelCopy = Readonly<{
13
+ title: string;
14
+ description: string;
15
+ locatorKindLabel: string;
16
+ locatorKinds: Record<SubjectLocatorKind, string>;
17
+ locatorLabel: string;
18
+ resolve: string;
19
+ resolving: string;
20
+ sectionsLabel: string;
21
+ request: string;
22
+ requesting: string;
23
+ requested: string;
24
+ granted: string;
25
+ }>;
26
+ export type SubjectAccessRequestPanelProps = Readonly<{
27
+ copy: SubjectAccessRequestPanelCopy;
28
+ locatorKind: SubjectLocatorKind;
29
+ locator: string;
30
+ status: SubjectAccessRequestStatus;
31
+ target?: ResolvedSubjectAccessTarget;
32
+ sections: readonly SubjectAccessSectionOption[];
33
+ error?: string;
34
+ onLocatorKindChange: (kind: SubjectLocatorKind) => void;
35
+ onLocatorChange: (locator: string) => void;
36
+ onResolve: () => void | Promise<void>;
37
+ onToggleSection: (code: string) => void;
38
+ onRequest: () => void | Promise<void>;
39
+ }>;
40
+ /**
41
+ * Controlled scanner/manual-locator and access-request surface.
42
+ *
43
+ * “Who scans” is intentionally absent from this contract. An individual
44
+ * member, professional or another later actor facade can reuse the same
45
+ * component; the authenticated BFF and GW decide identity and authorization.
46
+ */
47
+ export declare function SubjectAccessRequestPanel({ copy, locatorKind, locator, status, target, sections, error, onLocatorKindChange, onLocatorChange, onResolve, onToggleSection, onRequest, }: SubjectAccessRequestPanelProps): import("react").JSX.Element;
48
+ //# sourceMappingURL=SubjectAccessRequestPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubjectAccessRequestPanel.d.ts","sourceRoot":"","sources":["../src/SubjectAccessRequestPanel.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,cAAc,GAAG,SAAS,CAAA;AAClE,MAAM,MAAM,0BAA0B,GAClC,MAAM,GACN,WAAW,GACX,UAAU,GACV,YAAY,GACZ,WAAW,GACX,SAAS,GACT,OAAO,CAAA;AAEX,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,CAAA;CAClB,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAA;IAChD,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAC,CAAA;AAEF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,IAAI,EAAE,6BAA6B,CAAA;IACnC,WAAW,EAAE,kBAAkB,CAAA;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,0BAA0B,CAAA;IAClC,MAAM,CAAC,EAAE,2BAA2B,CAAA;IACpC,QAAQ,EAAE,SAAS,0BAA0B,EAAE,CAAA;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mBAAmB,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;IACvD,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,IAAI,EACJ,WAAW,EACX,OAAO,EACP,MAAM,EACN,MAAM,EACN,QAAQ,EACR,KAAK,EACL,mBAAmB,EACnB,eAAe,EACf,SAAS,EACT,eAAe,EACf,SAAS,GACV,EAAE,8BAA8B,+BA4EhC"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Controlled scanner/manual-locator and access-request surface.
4
+ *
5
+ * “Who scans” is intentionally absent from this contract. An individual
6
+ * member, professional or another later actor facade can reuse the same
7
+ * component; the authenticated BFF and GW decide identity and authorization.
8
+ */
9
+ export function SubjectAccessRequestPanel({ copy, locatorKind, locator, status, target, sections, error, onLocatorKindChange, onLocatorChange, onResolve, onToggleSection, onRequest, }) {
10
+ function resolve(event) {
11
+ event.preventDefault();
12
+ void onResolve();
13
+ }
14
+ function request(event) {
15
+ event.preventDefault();
16
+ void onRequest();
17
+ }
18
+ return (_jsxs("section", { className: "uhcp-access-panel", "aria-labelledby": "uhcp-access-request-title", children: [_jsx("h2", { id: "uhcp-access-request-title", children: copy.title }), _jsx("p", { children: copy.description }), _jsxs("form", { onSubmit: resolve, children: [_jsxs("label", { children: [_jsx("span", { children: copy.locatorKindLabel }), _jsx("select", { value: locatorKind, onChange: (event) => onLocatorKindChange(event.currentTarget.value), disabled: status === 'resolving', children: Object.entries(copy.locatorKinds).map(([kind, label]) => _jsx("option", { value: kind, children: label }, kind)) })] }), _jsxs("label", { children: [_jsx("span", { children: copy.locatorLabel }), _jsx("input", { value: locator, onChange: (event) => onLocatorChange(event.currentTarget.value), required: true, disabled: status === 'resolving' })] }), _jsx("button", { type: "submit", disabled: status === 'resolving' || !locator.trim(), children: status === 'resolving' ? copy.resolving : copy.resolve })] }), target && (_jsxs("form", { onSubmit: request, children: [_jsx("p", { children: _jsx("bdi", { children: target.display }) }), _jsxs("fieldset", { children: [_jsx("legend", { children: copy.sectionsLabel }), sections.map((section) => (_jsxs("label", { "data-section-code": section.code, children: [_jsx("input", { type: "checkbox", checked: section.selected, onChange: () => onToggleSection(section.code), disabled: status === 'requesting' || status === 'granted' }), _jsx("span", { children: section.label })] }, section.code)))] }), _jsx("button", { type: "submit", disabled: status === 'requesting'
19
+ || status === 'granted'
20
+ || !sections.some((section) => section.selected), children: status === 'requesting' ? copy.requesting : copy.request })] })), status === 'requested' && _jsx("p", { role: "status", children: copy.requested }), status === 'granted' && _jsx("p", { role: "status", children: copy.granted }), error && _jsx("p", { role: "alert", children: error })] }));
21
+ }
package/dist/index.d.ts CHANGED
@@ -7,4 +7,6 @@ export * from './PortalEntry.js';
7
7
  export * from './ProfileProtectionGate.js';
8
8
  export * from './ClinicalSectionSummary.js';
9
9
  export * from './IpsBundleSummary.js';
10
+ export * from './InvitationAcceptancePanel.js';
11
+ export * from './SubjectAccessRequestPanel.js';
10
12
  //# 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,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,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,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA"}
package/dist/index.js CHANGED
@@ -7,3 +7,5 @@ export * from './PortalEntry.js';
7
7
  export * from './ProfileProtectionGate.js';
8
8
  export * from './ClinicalSectionSummary.js';
9
9
  export * from './IpsBundleSummary.js';
10
+ export * from './InvitationAcceptancePanel.js';
11
+ export * from './SubjectAccessRequestPanel.js';
package/dist/styles.css CHANGED
@@ -184,6 +184,33 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
184
184
  .uhcp-clinical-title { font-weight: 800; margin: 0; }
185
185
  .uhcp-clinical-type { color: var(--uhcp-muted); font-size: .72rem; font-weight: 800; letter-spacing: .06em; margin: .35rem 0 0; text-transform: uppercase; }
186
186
  .uhcp-clinical-date, .uhcp-clinical-empty { color: var(--uhcp-muted); margin: .35rem 0 0; }
187
+ .uhcp-access-panel { color: var(--uhcp-text); display: grid; gap: 1rem; }
188
+ .uhcp-access-panel h2, .uhcp-access-panel p { margin: 0; }
189
+ .uhcp-access-panel > p { color: var(--uhcp-muted); line-height: 1.5; }
190
+ .uhcp-access-panel form { display: grid; gap: 1rem; }
191
+ .uhcp-access-panel label { display: grid; font-weight: 700; gap: .4rem; }
192
+ .uhcp-access-panel input, .uhcp-access-panel select {
193
+ background: var(--uhcp-panel);
194
+ border: 1px solid var(--uhcp-border);
195
+ border-radius: .75rem;
196
+ color: var(--uhcp-text);
197
+ font: inherit;
198
+ padding: .8rem;
199
+ }
200
+ .uhcp-access-panel fieldset { border: 1px solid var(--uhcp-border); border-radius: 1rem; display: grid; gap: .6rem; margin: 0; padding: 1rem; }
201
+ .uhcp-access-panel fieldset label { align-items: center; display: flex; }
202
+ .uhcp-access-panel button {
203
+ background: linear-gradient(135deg, var(--uhcp-action-start), var(--uhcp-action-end));
204
+ border: 0;
205
+ border-radius: .75rem;
206
+ color: #fff;
207
+ cursor: pointer;
208
+ font: inherit;
209
+ font-weight: 800;
210
+ padding: .8rem 1rem;
211
+ }
212
+ .uhcp-access-panel button:disabled { cursor: not-allowed; opacity: .55; }
213
+ .uhcp-access-panel [role="alert"] { color: #c64747; font-weight: 700; }
187
214
  .uhcp-entry-hero {
188
215
  align-items: center;
189
216
  background:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Brand-neutral React portal shell, account preferences and protected-profile UI for UHC and VetChain portals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "react-dom": ">=18.3.0"
28
28
  },
29
29
  "dependencies": {
30
- "gdc-common-utils-ts": "^2.3.10"
30
+ "gdc-common-utils-ts": "^2.3.12"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/react": "19.2.17",