uhc-portal-components-react 0.2.18 → 0.2.20

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
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.20 - 2026-10-21
6
+
7
+ - Add one accessible Personal subject workspace shared by UHC, PetChain and
8
+ SOSChain consumers, with five canonical screens and governed product-module
9
+ extensions.
10
+ - Add explicit human and animal clinical-summary profiles. The animal profile
11
+ reuses the same clinical screen while excluding Advance Directives for now.
12
+ - Keep the existing capability-driven professional subject workspace as the
13
+ shared UHC/VetChain composition; SOSChain layers emergency, Encounter and
14
+ communication capabilities on shared components rather than cloning them.
15
+ - Add one neutral notification composer screen for subject-addressed care
16
+ transitions and public-dataset alerts, with safe Markdown preview and host-
17
+ uploaded image/PDF attachment references.
18
+ - Define independent portal deployments as the supported reuse model while
19
+ requiring versioned complete screens, one typed public BFF path contract and
20
+ one capability manifest across web, mobile, telephone, chat and assistant
21
+ hosts.
5
22
  - Require confirmation for employee suspension, reactivation and permanent
6
23
  deletion; expose only valid lifecycle actions and distinguish warning,
7
24
  constructive and destructive actions with yellow, blue and red treatments.
package/README.md CHANGED
@@ -9,6 +9,8 @@ Publishable, brand-neutral React components shared by:
9
9
  - UHC Professional
10
10
  - VetChain Personal
11
11
  - VetChain Professional
12
+ - SOSChain Personal
13
+ - SOSChain Professional
12
14
 
13
15
  The package owns visual shell behavior only. Authentication, protected-profile
14
16
  storage, PIN verification and actor authorization remain in the host portal
@@ -74,9 +76,16 @@ The package deliberately exposes modules instead of a cloned application:
74
76
  authorization, fetching or persistence in the browser.
75
77
  - `AllClinicalSections<Person|Animal>`: lets each portal compose the same
76
78
  authorized section views as a stack or tabs, nest collapsible subsections and
77
- exclude inapplicable areas. Family history, care-team members and emergency
79
+ exclude inapplicable areas. `ClinicalSummaryProfiles.Human` retains Advance
80
+ Directives; the current `ClinicalSummaryProfiles.Animal` excludes that
81
+ human-only section while reusing the same screen. Family history, care-team members and emergency
78
82
  contacts remain independent; the organizer never follows another subject's
79
83
  card or documents automatically.
84
+ - `SubjectWorkspace`: canonical Personal navigation for Profile, Health, Care,
85
+ Communications and Contacts/Permissions. Product profiles may append an
86
+ explicit capability screen, such as animal mobility or SOS emergency work,
87
+ but tab selection never creates authority and all content remains host-
88
+ authorized.
80
89
  - `ClinicalTimeline` and `ClinicalDateTimeFields`: render longitudinal entries
81
90
  with localized dates, inclusive date/time ranges, multi-select clinical
82
91
  facets and accessible latest-first navigation. Editors capture calendar date
@@ -122,9 +131,11 @@ The package deliberately exposes modules instead of a cloned application:
122
131
  returned identifier evidence never grant subject access.
123
132
  resolver plus section selector. It deliberately has no scanner role: the
124
133
  same component is reusable by an individual member or a professional.
125
- - `SoschainWorkspace`: subject-card picker and four-area workspace for Places,
126
- devices/vehicles and read-only emergency communications. The host supplies
127
- already-authorized cards and capabilities.
134
+ - `SoschainWorkspace`: subject-card picker and capability composition for the
135
+ overview, health summary, care journey, emergency data, communications,
136
+ places and products screens. The host supplies already-authorized cards,
137
+ content and capabilities; SOS-specific emergency modules do not fork the
138
+ common Personal or Professional components.
128
139
  - `EncounterTransferConsole`: visual patient-transfer workspace. The host
129
140
  resolves the authorized subject, contacts and write capability; the component
130
141
  only reports the chosen source outcome (`on-hold` when returning or
@@ -193,6 +204,12 @@ management, licences, clinical data, care and permissions still live in the
193
204
  UHC host application and must be extracted behind host-owned adapters. A brand
194
205
  adapter may change tokens, logo, product vocabulary, animal-specific forms and
195
206
  route destinations; it must not fork shared flow or authorization behavior.
207
+ Independent deployments are expected: reuse means versioned complete screens,
208
+ one typed BFF path contract and one channel-neutral capability manifest, not a
209
+ mandatory shared deployed shell. Telephone and assistant templates consume
210
+ that same manifest and operation client so a subject-domain or jurisdiction
211
+ deployment changes trusted configuration and adapters rather than copying the
212
+ workflow.
196
213
 
197
214
  See [`docs/PERSONAL_FLOW_EXTRACTION.md`](docs/PERSONAL_FLOW_EXTRACTION.md) and
198
215
  the canonical cross-portal
@@ -10,9 +10,21 @@ export declare const ClinicalSectionNavigationIds: Readonly<{
10
10
  readonly ClinicalNotes: "clinical-notes";
11
11
  readonly CareTeamMembers: "care-team-members";
12
12
  readonly EmergencyContacts: "emergency-contacts";
13
+ readonly AdvanceDirectives: "advance-directives";
13
14
  }>;
14
15
  export type ClinicalSectionNavigationId = typeof ClinicalSectionNavigationIds[keyof typeof ClinicalSectionNavigationIds];
15
16
  export type ClinicalSubjectKind = 'person' | 'animal';
17
+ export declare const ClinicalSummaryProfiles: Readonly<{
18
+ readonly Human: Readonly<{
19
+ id: "human";
20
+ excludedSectionIds: readonly never[];
21
+ }>;
22
+ readonly Animal: Readonly<{
23
+ id: "animal";
24
+ excludedSectionIds: readonly "advance-directives"[];
25
+ }>;
26
+ }>;
27
+ export type ClinicalSummaryProfile = typeof ClinicalSummaryProfiles[keyof typeof ClinicalSummaryProfiles];
16
28
  export type AllClinicalSectionDefinition<SubjectKind extends ClinicalSubjectKind> = Readonly<{
17
29
  id: ClinicalSectionNavigationId | (string & {});
18
30
  title: string;
@@ -27,6 +39,7 @@ export type AllClinicalSectionsProps<SubjectKind extends ClinicalSubjectKind> =
27
39
  title: string;
28
40
  sections: readonly AllClinicalSectionDefinition<SubjectKind>[];
29
41
  excludedSectionIds?: readonly string[];
42
+ profile?: ClinicalSummaryProfile;
30
43
  presentation?: 'sections' | 'tabs';
31
44
  }>;
32
45
  /**
@@ -37,5 +50,5 @@ export type AllClinicalSectionsProps<SubjectKind extends ClinicalSubjectKind> =
37
50
  * contacts and care-team members remain independent domains; this component
38
51
  * never follows another subject's card or document links automatically.
39
52
  */
40
- export declare function AllClinicalSections<SubjectKind extends ClinicalSubjectKind>({ subjectKind, title, sections, excludedSectionIds, presentation, }: AllClinicalSectionsProps<SubjectKind>): import("react").JSX.Element;
53
+ export declare function AllClinicalSections<SubjectKind extends ClinicalSubjectKind>({ subjectKind, title, sections, excludedSectionIds, profile, presentation, }: AllClinicalSectionsProps<SubjectKind>): import("react").JSX.Element;
41
54
  //# sourceMappingURL=AllClinicalSections.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AllClinicalSections.d.ts","sourceRoot":"","sources":["../src/AllClinicalSections.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;EAW9B,CAAA;AAEX,MAAM,MAAM,2BAA2B,GAAG,OAAO,4BAA4B,CAAC,MAAM,OAAO,4BAA4B,CAAC,CAAA;AACxH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAErD,MAAM,MAAM,4BAA4B,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IAC3F,EAAE,EAAE,2BAA2B,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;CAChE,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IACvF,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;IAC9D,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,YAAY,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACnC,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,mBAAmB,EAAE,EAC3E,WAAW,EACX,KAAK,EACL,QAAQ,EACR,kBAAuB,EACvB,YAAyB,GAC1B,EAAE,wBAAwB,CAAC,WAAW,CAAC,+BAqCvC"}
1
+ {"version":3,"file":"AllClinicalSections.d.ts","sourceRoot":"","sources":["../src/AllClinicalSections.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;;EAY9B,CAAA;AAEX,MAAM,MAAM,2BAA2B,GAAG,OAAO,4BAA4B,CAAC,MAAM,OAAO,4BAA4B,CAAC,CAAA;AACxH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAErD,eAAO,MAAM,uBAAuB;;;;;;;;;EAGzB,CAAA;AAEX,MAAM,MAAM,sBAAsB,GAAG,OAAO,uBAAuB,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAA;AAEzG,MAAM,MAAM,4BAA4B,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IAC3F,EAAE,EAAE,2BAA2B,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;CAChE,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IACvF,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;IAC9D,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,YAAY,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACnC,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,mBAAmB,EAAE,EAC3E,WAAW,EACX,KAAK,EACL,QAAQ,EACR,kBAAuB,EACvB,OAAO,EACP,YAAyB,GAC1B,EAAE,wBAAwB,CAAC,WAAW,CAAC,+BAsCvC"}
@@ -11,6 +11,11 @@ export const ClinicalSectionNavigationIds = Object.freeze({
11
11
  ClinicalNotes: 'clinical-notes',
12
12
  CareTeamMembers: 'care-team-members',
13
13
  EmergencyContacts: 'emergency-contacts',
14
+ AdvanceDirectives: 'advance-directives',
15
+ });
16
+ export const ClinicalSummaryProfiles = Object.freeze({
17
+ Human: Object.freeze({ id: 'human', excludedSectionIds: Object.freeze([]) }),
18
+ Animal: Object.freeze({ id: 'animal', excludedSectionIds: Object.freeze([ClinicalSectionNavigationIds.AdvanceDirectives]) }),
14
19
  });
15
20
  /**
16
21
  * Organizes host-provided clinical views without redefining their FHIR model.
@@ -20,11 +25,12 @@ export const ClinicalSectionNavigationIds = Object.freeze({
20
25
  * contacts and care-team members remain independent domains; this component
21
26
  * never follows another subject's card or document links automatically.
22
27
  */
23
- export function AllClinicalSections({ subjectKind, title, sections, excludedSectionIds = [], presentation = 'sections', }) {
24
- const visibleSections = organizeClinicalSections(sections.filter(({ id }) => !excludedSectionIds.includes(id)));
28
+ export function AllClinicalSections({ subjectKind, title, sections, excludedSectionIds = [], profile, presentation = 'sections', }) {
29
+ const effectiveExcludedSectionIds = [...new Set([...(profile?.excludedSectionIds ?? []), ...excludedSectionIds])];
30
+ const visibleSections = organizeClinicalSections(sections.filter(({ id }) => !effectiveExcludedSectionIds.includes(id)));
25
31
  const [selectedId, setSelectedId] = useState(visibleSections[0]?.id ?? '');
26
32
  const selectedSection = visibleSections.find(({ id }) => id === selectedId) ?? visibleSections[0];
27
- return (_jsxs("section", { className: "uhcp-all-clinical-sections", "data-subject-kind": subjectKind, children: [_jsx("h2", { children: title }), presentation === 'tabs' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "uhcp-all-clinical-tabs", role: "tablist", "aria-label": title, children: visibleSections.map((section) => (_jsx("button", { type: "button", role: "tab", "aria-selected": section.id === selectedSection?.id, "aria-controls": `uhcp-all-section-${section.id}`, onClick: () => setSelectedId(section.id), children: section.title }, section.id))) }), selectedSection ? (_jsx("div", { id: `uhcp-all-section-${selectedSection.id}`, role: "tabpanel", children: _jsx(ClinicalSection, { definition: selectedSection, excludedSectionIds: excludedSectionIds }) })) : null] })) : (_jsx("div", { className: "uhcp-all-clinical-stack", children: visibleSections.map((section) => (_jsx(ClinicalSection, { definition: section, excludedSectionIds: excludedSectionIds }, section.id))) }))] }));
33
+ return (_jsxs("section", { className: "uhcp-all-clinical-sections", "data-subject-kind": subjectKind, children: [_jsx("h2", { children: title }), presentation === 'tabs' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "uhcp-all-clinical-tabs", role: "tablist", "aria-label": title, children: visibleSections.map((section) => (_jsx("button", { type: "button", role: "tab", "aria-selected": section.id === selectedSection?.id, "aria-controls": `uhcp-all-section-${section.id}`, onClick: () => setSelectedId(section.id), children: section.title }, section.id))) }), selectedSection ? (_jsx("div", { id: `uhcp-all-section-${selectedSection.id}`, role: "tabpanel", children: _jsx(ClinicalSection, { definition: selectedSection, excludedSectionIds: effectiveExcludedSectionIds }) })) : null] })) : (_jsx("div", { className: "uhcp-all-clinical-stack", children: visibleSections.map((section) => (_jsx(ClinicalSection, { definition: section, excludedSectionIds: effectiveExcludedSectionIds }, section.id))) }))] }));
28
34
  }
29
35
  function organizeClinicalSections(sections) {
30
36
  const ipsChildrenIds = new Set([
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from 'react';
2
+ export type CommunicationsWorkspaceTopic = Readonly<{
3
+ id: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ content?: ReactNode;
7
+ }>;
8
+ /** Shared personal-portal navigation; hosts provide channel and topic data through their own BFF adapters. */
9
+ export declare function CommunicationsWorkspace(input: Readonly<{
10
+ copy: Readonly<{
11
+ communicationsLabel: string;
12
+ channels: string;
13
+ infoAndAlerts: string;
14
+ }>;
15
+ channels: ReactNode;
16
+ alertTopics: readonly CommunicationsWorkspaceTopic[];
17
+ }>): import("react").JSX.Element;
18
+ //# sourceMappingURL=CommunicationsWorkspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommunicationsWorkspace.d.ts","sourceRoot":"","sources":["../src/CommunicationsWorkspace.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEvD,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAC,CAAA;AAEF,8GAA8G;AAC9G,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACtD,IAAI,EAAE,QAAQ,CAAC;QAAE,mBAAmB,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxF,QAAQ,EAAE,SAAS,CAAA;IACnB,WAAW,EAAE,SAAS,4BAA4B,EAAE,CAAA;CACrD,CAAC,+BAmBD"}
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useState } from 'react';
4
+ /** Shared personal-portal navigation; hosts provide channel and topic data through their own BFF adapters. */
5
+ export function CommunicationsWorkspace(input) {
6
+ const id = useId();
7
+ const [section, setSection] = useState('channels');
8
+ const firstEnabledTopic = input.alertTopics.find((topic) => !topic.disabled);
9
+ const [topicId, setTopicId] = useState(firstEnabledTopic?.id ?? '');
10
+ const activeTopic = input.alertTopics.find((topic) => topic.id === topicId && !topic.disabled);
11
+ return _jsxs("section", { className: "uhcp-communications-workspace", children: [_jsxs("div", { role: "tablist", "aria-label": input.copy.communicationsLabel, className: "uhcp-communications-tabs", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": section === 'channels', "aria-controls": `${id}-channels`, onClick: () => setSection('channels'), children: input.copy.channels }), _jsx("button", { type: "button", role: "tab", "aria-selected": section === 'alerts', "aria-controls": `${id}-alerts`, onClick: () => setSection('alerts'), children: input.copy.infoAndAlerts })] }), section === 'channels' ? _jsx("div", { id: `${id}-channels`, role: "tabpanel", children: input.channels }) : _jsxs("div", { id: `${id}-alerts`, role: "tabpanel", children: [_jsx("div", { role: "tablist", "aria-label": input.copy.infoAndAlerts, className: "uhcp-communications-topic-tabs", children: input.alertTopics.map((topic) => _jsx("button", { type: "button", role: "tab", disabled: topic.disabled, "aria-selected": activeTopic?.id === topic.id, onClick: () => setTopicId(topic.id), children: topic.label }, topic.id)) }), activeTopic?.content ? _jsx("div", { role: "tabpanel", children: activeTopic.content }) : null] })] });
12
+ }
@@ -0,0 +1,49 @@
1
+ import { type NotificationAttachment, type NotificationComposerAudience, type NotificationComposerDraft } from 'sos-data-utils-ts/public-notifications';
2
+ export type NotificationComposerAudienceOption = Readonly<{
3
+ id: string;
4
+ label: string;
5
+ description?: string;
6
+ audience: NotificationComposerAudience;
7
+ }>;
8
+ export type NotificationComposerCopy = Readonly<{
9
+ title: string;
10
+ audience: string;
11
+ subject: string;
12
+ bodyMarkdown: string;
13
+ preview: string;
14
+ attachments: string;
15
+ addAttachments: string;
16
+ removeAttachment: string;
17
+ send: string;
18
+ sending: string;
19
+ sent: string;
20
+ unsupportedAttachment: string;
21
+ attachmentUploadFailed: string;
22
+ sendFailed: string;
23
+ priority?: string;
24
+ validUntil?: string;
25
+ }>;
26
+ export type NotificationComposerFormSchema = Readonly<{
27
+ $id: string;
28
+ required: readonly string[];
29
+ properties: Readonly<Record<string, Readonly<{
30
+ type: string;
31
+ enum?: readonly string[];
32
+ maxLength?: number;
33
+ maxItems?: number;
34
+ }>>>;
35
+ }>;
36
+ /**
37
+ * Channel-neutral notification editor. The host supplies BFF-authorized
38
+ * audiences plus upload/send adapters; this component never accepts direct
39
+ * recipient addresses, subscriber lists or publishing authority.
40
+ */
41
+ export declare function NotificationComposer(input: Readonly<{
42
+ audiences: readonly NotificationComposerAudienceOption[];
43
+ copy: NotificationComposerCopy;
44
+ uploadAttachment(file: File): Promise<NotificationAttachment>;
45
+ onSubmit(draft: NotificationComposerDraft): Promise<void>;
46
+ formSchema?: NotificationComposerFormSchema;
47
+ disabled?: boolean;
48
+ }>): import("react").JSX.Element;
49
+ //# sourceMappingURL=NotificationComposer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationComposer.d.ts","sourceRoot":"","sources":["../src/NotificationComposer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAE/B,MAAM,wCAAwC,CAAA;AAG/C,MAAM,MAAM,kCAAkC,GAAG,QAAQ,CAAC;IACxD,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,4BAA4B,CAAA;CACvC,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,qBAAqB,EAAE,MAAM,CAAA;IAC7B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAC,CAAA;AAEF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC3C,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;QACxB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAC,CAAC,CAAC,CAAA;CACL,CAAC,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC;IACnD,SAAS,EAAE,SAAS,kCAAkC,EAAE,CAAA;IACxD,IAAI,EAAE,wBAAwB,CAAA;IAC9B,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAA;IAC7D,QAAQ,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,UAAU,CAAC,EAAE,8BAA8B,CAAA;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC,+BAmKD"}
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useState } from 'react';
4
+ import { normalizeNotificationComposerDraft, NotificationAttachmentMediaTypes, PublicAlertPriorities, } from 'sos-data-utils-ts/public-notifications';
5
+ import { LegalMarkdownDocument } from './LegalMarkdownDocument.js';
6
+ /**
7
+ * Channel-neutral notification editor. The host supplies BFF-authorized
8
+ * audiences plus upload/send adapters; this component never accepts direct
9
+ * recipient addresses, subscriber lists or publishing authority.
10
+ */
11
+ export function NotificationComposer(input) {
12
+ const id = useId();
13
+ const [audienceId, setAudienceId] = useState(input.audiences[0]?.id ?? '');
14
+ const [subject, setSubject] = useState('');
15
+ const [bodyMarkdown, setBodyMarkdown] = useState('');
16
+ const [priority, setPriority] = useState('routine');
17
+ const [validUntil, setValidUntil] = useState('');
18
+ const [attachments, setAttachments] = useState([]);
19
+ const [working, setWorking] = useState(false);
20
+ const [status, setStatus] = useState('');
21
+ const [error, setError] = useState('');
22
+ const audience = input.audiences.find((candidate) => candidate.id === audienceId);
23
+ const disabled = Boolean(input.disabled || working);
24
+ const publicAlertForm = Boolean(input.formSchema);
25
+ const priorityOptions = input.formSchema?.properties.priority?.enum ?? PublicAlertPriorities;
26
+ const subjectMaxLength = input.formSchema?.properties.subject?.maxLength;
27
+ const bodyMaxLength = input.formSchema?.properties.bodyMarkdown?.maxLength;
28
+ const attachmentLimit = input.formSchema?.properties.attachments?.maxItems;
29
+ async function addAttachments(files) {
30
+ if (!files?.length)
31
+ return;
32
+ setError('');
33
+ const selectedFiles = Array.from(files);
34
+ if (attachmentLimit !== undefined && attachments.length + selectedFiles.length > attachmentLimit) {
35
+ setError(input.copy.unsupportedAttachment);
36
+ return;
37
+ }
38
+ if (selectedFiles.some((file) => !NotificationAttachmentMediaTypes.includes(file.type))) {
39
+ setError(input.copy.unsupportedAttachment);
40
+ return;
41
+ }
42
+ setWorking(true);
43
+ try {
44
+ const uploaded = await Promise.all(selectedFiles.map((file) => input.uploadAttachment(file)));
45
+ setAttachments((current) => Object.freeze([...current, ...uploaded]));
46
+ }
47
+ catch {
48
+ setError(input.copy.attachmentUploadFailed);
49
+ }
50
+ finally {
51
+ setWorking(false);
52
+ }
53
+ }
54
+ async function submit() {
55
+ if (!audience || !subject.trim() || !bodyMarkdown.trim())
56
+ return;
57
+ setWorking(true);
58
+ setStatus('');
59
+ setError('');
60
+ try {
61
+ const draft = normalizeNotificationComposerDraft({
62
+ audience: audience.audience,
63
+ subject,
64
+ bodyMarkdown,
65
+ attachments,
66
+ ...(publicAlertForm ? {
67
+ priority,
68
+ validUntil: new Date(validUntil).toISOString(),
69
+ } : {}),
70
+ });
71
+ await input.onSubmit(draft);
72
+ setStatus(input.copy.sent);
73
+ }
74
+ catch {
75
+ setError(input.copy.sendFailed);
76
+ }
77
+ finally {
78
+ setWorking(false);
79
+ }
80
+ }
81
+ return _jsxs("section", { className: "uhcp-notification-composer", "aria-labelledby": `${id}-title`, "data-form-schema": input.formSchema?.$id, children: [_jsx("header", { children: _jsx("h2", { id: `${id}-title`, children: input.copy.title }) }), _jsx("label", { htmlFor: `${id}-audience`, children: input.copy.audience }), _jsx("select", { id: `${id}-audience`, value: audienceId, disabled: disabled, onChange: (event) => setAudienceId(event.currentTarget.value), children: input.audiences.map((option) => _jsx("option", { value: option.id, children: option.label }, option.id)) }), audience?.description ? _jsx("p", { className: "uhcp-muted", children: audience.description }) : null, _jsx("label", { htmlFor: `${id}-subject`, children: input.copy.subject }), _jsx("input", { id: `${id}-subject`, value: subject, maxLength: subjectMaxLength, disabled: disabled, onChange: (event) => setSubject(event.currentTarget.value) }), publicAlertForm ? _jsxs(_Fragment, { children: [_jsx("label", { htmlFor: `${id}-priority`, children: input.copy.priority || 'Priority' }), _jsx("select", { id: `${id}-priority`, value: priority, disabled: disabled, onChange: (event) => setPriority(event.currentTarget.value), children: priorityOptions.map((value) => _jsx("option", { value: value, children: value }, value)) }), _jsx("label", { htmlFor: `${id}-valid-until`, children: input.copy.validUntil || 'Valid until' }), _jsx("input", { id: `${id}-valid-until`, type: "datetime-local", value: validUntil, required: true, disabled: disabled, onChange: (event) => setValidUntil(event.currentTarget.value) })] }) : null, _jsxs("div", { className: "uhcp-notification-composer-body", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: `${id}-body`, children: input.copy.bodyMarkdown }), _jsx("textarea", { id: `${id}-body`, value: bodyMarkdown, maxLength: bodyMaxLength, disabled: disabled, onChange: (event) => setBodyMarkdown(event.currentTarget.value) })] }), _jsxs("section", { "aria-labelledby": `${id}-preview`, children: [_jsx("h3", { id: `${id}-preview`, children: input.copy.preview }), _jsx(LegalMarkdownDocument, { source: bodyMarkdown })] })] }), _jsxs("fieldset", { disabled: disabled, children: [_jsx("legend", { children: input.copy.attachments }), _jsx("label", { htmlFor: `${id}-attachments`, children: input.copy.addAttachments }), _jsx("input", { id: `${id}-attachments`, type: "file", multiple: true, accept: NotificationAttachmentMediaTypes.join(','), onChange: (event) => void addAttachments(event.currentTarget.files) }), attachments.length > 0 ? _jsx("ul", { children: attachments.map((attachment) => _jsxs("li", { children: [_jsx("span", { children: attachment.fileName }), _jsx("button", { type: "button", onClick: () => setAttachments((current) => current.filter((item) => item.attachmentId !== attachment.attachmentId)), children: input.copy.removeAttachment })] }, attachment.attachmentId)) }) : null] }), _jsx("button", { type: "button", disabled: disabled || !audience || !subject.trim() || !bodyMarkdown.trim() || (publicAlertForm && !validUntil), onClick: () => void submit(), children: working ? input.copy.sending : input.copy.send }), status ? _jsx("p", { role: "status", children: status }) : null, error ? _jsx("p", { role: "alert", children: error }) : null] });
82
+ }
@@ -0,0 +1,32 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare const SELECTED_SUBJECT_AREAS: readonly ["identity", "health", "insurance", "appointments-tasks", "communications", "contacts-permissions"];
3
+ export type SelectedSubjectArea = (typeof SELECTED_SUBJECT_AREAS)[number];
4
+ export type SelectedSubjectAction = 'search' | 'read' | 'create' | 'update' | 'delete';
5
+ export type SelectedSubjectAreaCapability = Readonly<{
6
+ area: SelectedSubjectArea;
7
+ actions: readonly SelectedSubjectAction[];
8
+ recordCount?: number;
9
+ }>;
10
+ export type SelectedSubjectWorkspaceCopy = Readonly<{
11
+ card: string;
12
+ summary: string;
13
+ identity: string;
14
+ health: string;
15
+ insurance: string;
16
+ appointmentsTasks: string;
17
+ communications: string;
18
+ contactsPermissions: string;
19
+ records: string;
20
+ }>;
21
+ /** Capability-driven professional subject shell; selection never grants an action. */
22
+ export declare function SelectedSubjectWorkspace({ subject, capabilities, copy, content, initialArea }: Readonly<{
23
+ subject: Readonly<{
24
+ id: string;
25
+ display: string;
26
+ }>;
27
+ capabilities: readonly SelectedSubjectAreaCapability[];
28
+ copy: SelectedSubjectWorkspaceCopy;
29
+ content?: Partial<Record<SelectedSubjectArea, ReactNode>>;
30
+ initialArea?: 'card' | SelectedSubjectArea;
31
+ }>): import("react").JSX.Element;
32
+ //# sourceMappingURL=SelectedSubjectWorkspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectedSubjectWorkspace.d.ts","sourceRoot":"","sources":["../src/SelectedSubjectWorkspace.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpE,eAAO,MAAM,sBAAsB,8GAA+G,CAAA;AAClJ,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAA;AACzE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACtF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AACpJ,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAc5O,sFAAsF;AACtF,wBAAgB,wBAAwB,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,OAAY,EAAE,WAAoB,EAAE,EAAE,QAAQ,CAAC;IACrH,OAAO,EAAE,QAAQ,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,YAAY,EAAE,SAAS,6BAA6B,EAAE,CAAA;IACtD,IAAI,EAAE,4BAA4B,CAAA;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC,CAAA;IACzD,WAAW,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAA;CAC3C,CAAC,+BAmBD"}
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ export const SELECTED_SUBJECT_AREAS = ['identity', 'health', 'insurance', 'appointments-tasks', 'communications', 'contacts-permissions'];
5
+ const copyKey = { identity: 'identity', health: 'health', insurance: 'insurance', 'appointments-tasks': 'appointmentsTasks', communications: 'communications', 'contacts-permissions': 'contactsPermissions' };
6
+ function mergeCapabilities(capabilities) {
7
+ const merged = new Map();
8
+ for (const capability of capabilities) {
9
+ if (!capability.actions.length)
10
+ continue;
11
+ const previous = merged.get(capability.area);
12
+ merged.set(capability.area, { area: capability.area, actions: [...new Set([...(previous?.actions ?? []), ...capability.actions])], recordCount: capability.actions.includes('search') ? capability.recordCount ?? previous?.recordCount : previous?.recordCount });
13
+ }
14
+ return merged;
15
+ }
16
+ /** Capability-driven professional subject shell; selection never grants an action. */
17
+ export function SelectedSubjectWorkspace({ subject, capabilities, copy, content = {}, initialArea = 'card' }) {
18
+ const capabilityByArea = useMemo(() => mergeCapabilities(capabilities), [capabilities]);
19
+ const visibleAreas = SELECTED_SUBJECT_AREAS.filter((area) => capabilityByArea.has(area));
20
+ const [selectedArea, setSelectedArea] = useState(initialArea);
21
+ useEffect(() => { if (selectedArea !== 'card' && !capabilityByArea.has(selectedArea))
22
+ setSelectedArea('card'); }, [capabilityByArea, selectedArea]);
23
+ const labelFor = (area) => {
24
+ const label = copy[copyKey[area]];
25
+ const capability = capabilityByArea.get(area);
26
+ return capability?.actions.includes('search') && capability.recordCount !== undefined ? `${label}, ${capability.recordCount} ${copy.records}` : label;
27
+ };
28
+ const selectedCapability = selectedArea === 'card' ? undefined : capabilityByArea.get(selectedArea);
29
+ return _jsxs("section", { className: "uhcp-selected-subject-workspace", "aria-labelledby": "uhcp-selected-subject-title", children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", { children: copy.summary }), _jsx("h2", { id: "uhcp-selected-subject-title", children: subject.display })] }), _jsx("code", { children: subject.id })] }), _jsxs("div", { className: "uhcp-selected-subject-tabs", role: "tablist", "aria-label": subject.display, children: [_jsx("button", { type: "button", role: "tab", "aria-selected": selectedArea === 'card', onClick: () => setSelectedArea('card'), children: copy.card }), visibleAreas.map((area) => _jsx("button", { type: "button", role: "tab", "aria-selected": selectedArea === area, onClick: () => setSelectedArea(area), children: labelFor(area) }, area))] }), _jsx("div", { role: "tabpanel", children: selectedArea === 'card' ? _jsxs("div", { children: [_jsx("h3", { children: copy.summary }), _jsx("ul", { children: visibleAreas.map((area) => _jsx("li", { children: labelFor(area) }, area)) })] }) : selectedCapability?.actions.includes('read') ? content[selectedArea] : null })] });
30
+ }
@@ -6,6 +6,7 @@ export type SoschainCardSummary = Readonly<{
6
6
  identifier: string;
7
7
  displayName: string;
8
8
  kindLabel: string;
9
+ subjectKind?: 'person' | 'animal';
9
10
  selected?: boolean;
10
11
  openHref: string;
11
12
  }>;
@@ -82,6 +83,8 @@ export type SoschainWorkspaceProps = Readonly<{
82
83
  places: readonly SoschainAssociationSummary[];
83
84
  products: readonly SoschainAssociationSummary[];
84
85
  communications: readonly SoschainCommunicationSummary[];
86
+ /** Shared channels/topics screen composed with host-owned BFF adapters. */
87
+ communicationsContent?: ReactNode;
85
88
  /** Host-authorized IPS/clinical projection, normally `IpsBundleSummary`. */
86
89
  healthSummary?: ReactNode;
87
90
  /** False only when the host has already resolved a current write capability. */
@@ -109,5 +112,5 @@ export type SoschainWorkspaceProps = Readonly<{
109
112
  * communications. This presentational component never derives authority from
110
113
  * a selected role, card, tab or browser-controlled value.
111
114
  */
112
- export declare function SoschainWorkspace({ copy, cards, selectedCard, activeArea, areaHrefs, places, products, communications, healthSummary, clinicalReadOnly, careJourney, emergencyData, canManageAssociations, addPlaceHref, addProductHref, importCardHref, overviewContent, primaryAreas, moreLabel, showCardIdentifier, onSelectCard, }: SoschainWorkspaceProps): import("react").JSX.Element;
115
+ export declare function SoschainWorkspace({ copy, cards, selectedCard, activeArea, areaHrefs, places, products, communications, communicationsContent, healthSummary, clinicalReadOnly, careJourney, emergencyData, canManageAssociations, addPlaceHref, addProductHref, importCardHref, overviewContent, primaryAreas, moreLabel, showCardIdentifier, onSelectCard, }: SoschainWorkspaceProps): import("react").JSX.Element;
113
116
  //# sourceMappingURL=SoschainWorkspace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SoschainWorkspace.d.ts","sourceRoot":"","sources":["../src/SoschainWorkspace.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SoschainWorkspace.d.ts","sourceRoot":"","sources":["../src/SoschainWorkspace.tsx"],"names":[],"mappings":"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"}
@@ -21,7 +21,7 @@ function AssociationList({ items, empty }) {
21
21
  * communications. This presentational component never derives authority from
22
22
  * a selected role, card, tab or browser-controlled value.
23
23
  */
24
- export function SoschainWorkspace({ copy, cards, selectedCard, activeArea, areaHrefs, places, products, communications, healthSummary, clinicalReadOnly = true, careJourney = [], emergencyData = [], canManageAssociations, addPlaceHref, addProductHref, importCardHref, overviewContent, primaryAreas, moreLabel = 'More', showCardIdentifier = true, onSelectCard, }) {
24
+ export function SoschainWorkspace({ copy, cards, selectedCard, activeArea, areaHrefs, places, products, communications, communicationsContent, healthSummary, clinicalReadOnly = true, careJourney = [], emergencyData = [], canManageAssociations, addPlaceHref, addProductHref, importCardHref, overviewContent, primaryAreas, moreLabel = 'More', showCardIdentifier = true, onSelectCard, }) {
25
25
  if (!selectedCard) {
26
26
  return (_jsxs("section", { className: "uhcp-sos-wrap", "aria-labelledby": "sos-card-picker-title", children: [_jsxs("header", { className: "uhcp-sos-heading", children: [_jsx("span", { children: copy.workspaceEyebrow }), _jsx("h1", { id: "sos-card-picker-title", children: copy.cardsTitle }), _jsx("p", { children: copy.cardsDescription }), importCardHref && copy.importCard ? _jsxs("a", { href: importCardHref, children: [copy.importCard, " \u2192"] }) : null] }), cards.length === 0 ? (_jsxs("div", { className: "uhcp-sos-empty-panel", children: [_jsx("h2", { children: copy.noCardTitle }), _jsx("p", { children: copy.noCardBody })] })) : (_jsx("ul", { className: "uhcp-sos-card-grid", children: cards.map((card) => (_jsx("li", { children: onSelectCard ? _jsxs("button", { type: "button", onClick: () => onSelectCard(card.identifier), children: [_jsx("span", { children: card.kindLabel }), _jsx("strong", { children: card.displayName }), showCardIdentifier ? _jsx("small", { children: card.identifier }) : null, _jsxs("b", { children: [copy.openCard, " \u2192"] })] }) : _jsxs("a", { href: card.openHref, children: [_jsx("span", { children: card.kindLabel }), _jsx("strong", { children: card.displayName }), showCardIdentifier ? _jsx("small", { children: card.identifier }) : null, _jsxs("b", { children: [copy.openCard, " \u2192"] })] }) }, card.identifier))) }))] }));
27
27
  }
@@ -53,5 +53,5 @@ export function SoschainWorkspace({ copy, cards, selectedCard, activeArea, areaH
53
53
  : copy.overviewBody;
54
54
  const clinicalAreaIsReadOnly = activeArea === 'care-journey' || (activeArea === 'health-summary' && clinicalReadOnly);
55
55
  const associationAreaIsReadOnly = !canManageAssociations && (activeArea === 'overview' || activeArea === 'places' || activeArea === 'products');
56
- return (_jsxs("section", { className: "uhcp-sos-wrap", children: [_jsxs("header", { className: "uhcp-sos-heading", children: [_jsx("span", { children: copy.selectedCard }), _jsx("h1", { children: selectedCard.displayName }), _jsxs("p", { children: [selectedCard.kindLabel, showCardIdentifier ? ` · ${selectedCard.identifier}` : ''] })] }), _jsxs("nav", { className: "uhcp-sos-tabs", "aria-label": copy.workspaceEyebrow, children: [(primaryAreas ?? SOSCHAIN_SUBJECT_WORKSPACE_AREAS).map((area) => (_jsx("a", { href: areaHrefs[area], "aria-current": activeArea === area ? 'page' : undefined, children: copy.areas[area] }, area))), primaryAreas ? _jsxs("details", { className: "uhcp-sos-more", children: [_jsx("summary", { children: moreLabel }), _jsx("div", { children: SOSCHAIN_SUBJECT_WORKSPACE_AREAS.filter((area) => !primaryAreas.includes(area)).map((area) => (_jsx("a", { href: areaHrefs[area], "aria-current": activeArea === area ? 'page' : undefined, children: copy.areas[area] }, area))) })] }) : null] }), _jsxs("div", { className: "uhcp-sos-panel", children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h2", { children: title }), _jsx("p", { children: body })] }), (clinicalAreaIsReadOnly || associationAreaIsReadOnly) && (_jsx("span", { className: "uhcp-sos-readonly", children: copy.readOnly })), canManageAssociations && activeArea === 'places' && addPlaceHref && _jsx("a", { href: addPlaceHref, children: copy.addPlace }), canManageAssociations && activeArea === 'products' && addProductHref && _jsx("a", { href: addProductHref, children: copy.addProduct })] }), activeArea === 'overview' && (overviewContent ?? (_jsxs("div", { className: "uhcp-sos-overview-grid", children: [_jsxs("article", { children: [_jsx("strong", { children: places.length }), _jsx("span", { children: copy.areas.places })] }), _jsxs("article", { children: [_jsx("strong", { children: products.length }), _jsx("span", { children: copy.areas.products })] }), _jsxs("article", { children: [_jsx("strong", { children: communications.length }), _jsx("span", { children: copy.areas.communications })] }), _jsxs("article", { children: [_jsx("strong", { children: careJourney.length }), _jsx("span", { children: copy.areas['care-journey'] })] })] }))), activeArea === 'health-summary' && (healthSummary ?? _jsx("p", { className: "uhcp-sos-empty", children: copy.emptyHealthSummary })), activeArea === 'care-journey' && (careJourney.length === 0 ? _jsx("p", { className: "uhcp-sos-empty", children: copy.emptyCareJourney }) : (_jsx("ol", { className: "uhcp-sos-timeline uhcp-sos-care-journey", children: careJourney.map((encounter) => (_jsxs("li", { children: [_jsxs("div", { children: [_jsx("span", { children: encounter.statusLabel }), _jsx("time", { dateTime: encounter.periodStart, children: encounter.periodStart })] }), _jsx("strong", { children: encounter.serviceLabel }), encounter.detail ? _jsx("p", { children: encounter.detail }) : null, encounter.locationLabel || encounter.periodEnd ? _jsx("small", { children: [encounter.locationLabel, encounter.periodEnd].filter(Boolean).join(' · ') }) : null] }, encounter.identifier))) }))), activeArea === 'emergency-data' && _jsx(ProtectedIndexEmergencyData, { copy: copy.emergencyData, items: emergencyData }), activeArea === 'places' && _jsx(AssociationList, { items: places, empty: copy.emptyPlaces }), activeArea === 'products' && _jsx(AssociationList, { items: products, empty: copy.emptyProducts }), activeArea === 'communications' && (communications.length === 0 ? _jsx("p", { className: "uhcp-sos-empty", children: copy.emptyCommunications }) : (_jsx("ol", { className: "uhcp-sos-timeline", children: communications.map((message) => (_jsxs("li", { children: [_jsxs("div", { children: [_jsx("span", { children: message.categoryLabel }), _jsx("time", { children: message.occurredAt })] }), _jsx("strong", { children: message.subject }), _jsx("p", { children: message.body }), _jsxs("small", { children: [message.sender, message.placeLabel ? ` · ${message.placeLabel}` : ''] })] }, message.identifier))) })))] })] }));
56
+ return (_jsxs("section", { className: "uhcp-sos-wrap", children: [_jsxs("header", { className: "uhcp-sos-heading", children: [_jsx("span", { children: copy.selectedCard }), _jsx("h1", { children: selectedCard.displayName }), _jsxs("p", { children: [selectedCard.kindLabel, showCardIdentifier ? ` · ${selectedCard.identifier}` : ''] })] }), _jsxs("nav", { className: "uhcp-sos-tabs", "aria-label": copy.workspaceEyebrow, children: [(primaryAreas ?? SOSCHAIN_SUBJECT_WORKSPACE_AREAS).map((area) => (_jsx("a", { href: areaHrefs[area], "aria-current": activeArea === area ? 'page' : undefined, children: copy.areas[area] }, area))), primaryAreas ? _jsxs("details", { className: "uhcp-sos-more", children: [_jsx("summary", { children: moreLabel }), _jsx("div", { children: SOSCHAIN_SUBJECT_WORKSPACE_AREAS.filter((area) => !primaryAreas.includes(area)).map((area) => (_jsx("a", { href: areaHrefs[area], "aria-current": activeArea === area ? 'page' : undefined, children: copy.areas[area] }, area))) })] }) : null] }), _jsxs("div", { className: "uhcp-sos-panel", children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h2", { children: title }), _jsx("p", { children: body })] }), (clinicalAreaIsReadOnly || associationAreaIsReadOnly) && (_jsx("span", { className: "uhcp-sos-readonly", children: copy.readOnly })), canManageAssociations && activeArea === 'places' && addPlaceHref && _jsx("a", { href: addPlaceHref, children: copy.addPlace }), canManageAssociations && activeArea === 'products' && addProductHref && _jsx("a", { href: addProductHref, children: copy.addProduct })] }), activeArea === 'overview' && (overviewContent ?? (_jsxs("div", { className: "uhcp-sos-overview-grid", children: [_jsxs("article", { children: [_jsx("strong", { children: places.length }), _jsx("span", { children: copy.areas.places })] }), _jsxs("article", { children: [_jsx("strong", { children: products.length }), _jsx("span", { children: copy.areas.products })] }), _jsxs("article", { children: [_jsx("strong", { children: communications.length }), _jsx("span", { children: copy.areas.communications })] }), _jsxs("article", { children: [_jsx("strong", { children: careJourney.length }), _jsx("span", { children: copy.areas['care-journey'] })] })] }))), activeArea === 'health-summary' && (healthSummary ?? _jsx("p", { className: "uhcp-sos-empty", children: copy.emptyHealthSummary })), activeArea === 'care-journey' && (careJourney.length === 0 ? _jsx("p", { className: "uhcp-sos-empty", children: copy.emptyCareJourney }) : (_jsx("ol", { className: "uhcp-sos-timeline uhcp-sos-care-journey", children: careJourney.map((encounter) => (_jsxs("li", { children: [_jsxs("div", { children: [_jsx("span", { children: encounter.statusLabel }), _jsx("time", { dateTime: encounter.periodStart, children: encounter.periodStart })] }), _jsx("strong", { children: encounter.serviceLabel }), encounter.detail ? _jsx("p", { children: encounter.detail }) : null, encounter.locationLabel || encounter.periodEnd ? _jsx("small", { children: [encounter.locationLabel, encounter.periodEnd].filter(Boolean).join(' · ') }) : null] }, encounter.identifier))) }))), activeArea === 'emergency-data' && _jsx(ProtectedIndexEmergencyData, { copy: copy.emergencyData, items: emergencyData }), activeArea === 'places' && _jsx(AssociationList, { items: places, empty: copy.emptyPlaces }), activeArea === 'products' && _jsx(AssociationList, { items: products, empty: copy.emptyProducts }), activeArea === 'communications' && (communicationsContent ?? (communications.length === 0 ? _jsx("p", { className: "uhcp-sos-empty", children: copy.emptyCommunications }) : (_jsx("ol", { className: "uhcp-sos-timeline", children: communications.map((message) => (_jsxs("li", { children: [_jsxs("div", { children: [_jsx("span", { children: message.categoryLabel }), _jsx("time", { children: message.occurredAt })] }), _jsx("strong", { children: message.subject }), _jsx("p", { children: message.body }), _jsxs("small", { children: [message.sender, message.placeLabel ? ` · ${message.placeLabel}` : ''] })] }, message.identifier))) }))))] })] }));
57
57
  }
@@ -0,0 +1,29 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare const PERSONAL_SUBJECT_WORKSPACE_AREAS: readonly ["profile", "health", "care", "communications", "access"];
3
+ export type PersonalSubjectWorkspaceAreaId = typeof PERSONAL_SUBJECT_WORKSPACE_AREAS[number];
4
+ export type SubjectWorkspaceArea = Readonly<{
5
+ id: string;
6
+ label: string;
7
+ content: ReactNode;
8
+ icon?: ReactNode;
9
+ disabled?: boolean;
10
+ }>;
11
+ export type SubjectWorkspaceProps = Readonly<{
12
+ ariaLabel: string;
13
+ areas: readonly SubjectWorkspaceArea[];
14
+ activeArea?: string;
15
+ initialArea?: string;
16
+ onAreaChange?: (area: string) => void;
17
+ className?: string;
18
+ tabListClassName?: string;
19
+ tabClassName?: string;
20
+ panelClassName?: string;
21
+ }>;
22
+ /**
23
+ * Shared subject-screen shell for Personal portals.
24
+ *
25
+ * Hosts provide already-authorized content and product-specific additions.
26
+ * Selecting a tab changes presentation only and never grants data access.
27
+ */
28
+ export declare function SubjectWorkspace({ ariaLabel, areas, activeArea, initialArea, onAreaChange, className, tabListClassName, tabClassName, panelClassName, }: SubjectWorkspaceProps): import("react").JSX.Element;
29
+ //# sourceMappingURL=SubjectWorkspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubjectWorkspace.d.ts","sourceRoot":"","sources":["../src/SubjectWorkspace.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAkD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtF,eAAO,MAAM,gCAAgC,oEAMnC,CAAA;AAEV,MAAM,MAAM,8BAA8B,GAAG,OAAO,gCAAgC,CAAC,MAAM,CAAC,CAAA;AAC5F,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS,CAAA;IAClB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,KAAK,EACL,UAAU,EACV,WAAW,EACX,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,EAAE,qBAAqB,+BAyDvB"}
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useId, useState } from 'react';
4
+ export const PERSONAL_SUBJECT_WORKSPACE_AREAS = [
5
+ 'profile',
6
+ 'health',
7
+ 'care',
8
+ 'communications',
9
+ 'access',
10
+ ];
11
+ /**
12
+ * Shared subject-screen shell for Personal portals.
13
+ *
14
+ * Hosts provide already-authorized content and product-specific additions.
15
+ * Selecting a tab changes presentation only and never grants data access.
16
+ */
17
+ export function SubjectWorkspace({ ariaLabel, areas, activeArea, initialArea, onAreaChange, className, tabListClassName, tabClassName, panelClassName, }) {
18
+ const firstEnabledArea = areas.find(({ disabled }) => !disabled)?.id ?? '';
19
+ const [uncontrolledArea, setUncontrolledArea] = useState(initialArea ?? firstEnabledArea);
20
+ const requestedArea = activeArea ?? uncontrolledArea;
21
+ const selectedArea = areas.some(({ id, disabled }) => id === requestedArea && !disabled)
22
+ ? requestedArea
23
+ : firstEnabledArea;
24
+ const instanceId = useId().replaceAll(':', '');
25
+ useEffect(() => {
26
+ if (activeArea === undefined && uncontrolledArea !== selectedArea)
27
+ setUncontrolledArea(selectedArea);
28
+ }, [activeArea, selectedArea, uncontrolledArea]);
29
+ function select(area) {
30
+ if (area.disabled)
31
+ return;
32
+ if (activeArea === undefined)
33
+ setUncontrolledArea(area.id);
34
+ onAreaChange?.(area.id);
35
+ }
36
+ function moveWithKeyboard(event, area) {
37
+ const enabledAreas = areas.filter(({ disabled }) => !disabled);
38
+ const currentIndex = enabledAreas.findIndex(({ id }) => id === area.id);
39
+ let nextIndex;
40
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown')
41
+ nextIndex = (currentIndex + 1) % enabledAreas.length;
42
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
43
+ nextIndex = (currentIndex - 1 + enabledAreas.length) % enabledAreas.length;
44
+ if (event.key === 'Home')
45
+ nextIndex = 0;
46
+ if (event.key === 'End')
47
+ nextIndex = enabledAreas.length - 1;
48
+ if (nextIndex === undefined || !enabledAreas[nextIndex])
49
+ return;
50
+ event.preventDefault();
51
+ const nextArea = enabledAreas[nextIndex];
52
+ select(nextArea);
53
+ document.getElementById(`${instanceId}-tab-${nextArea.id}`)?.focus();
54
+ }
55
+ return _jsxs("section", { className: className ?? 'uhcp-subject-workspace', children: [_jsx("div", { className: tabListClassName ?? 'uhcp-subject-workspace-tabs', role: "tablist", "aria-label": ariaLabel, children: areas.map((area) => _jsxs("button", { id: `${instanceId}-tab-${area.id}`, type: "button", role: "tab", "aria-selected": selectedArea === area.id, "aria-controls": `${instanceId}-panel-${area.id}`, disabled: area.disabled, className: tabClassName, onClick: () => select(area), onKeyDown: (event) => moveWithKeyboard(event, area), children: [area.icon, area.label] }, area.id)) }), areas.map((area) => selectedArea === area.id ? _jsx("div", { id: `${instanceId}-panel-${area.id}`, role: "tabpanel", "aria-labelledby": `${instanceId}-tab-${area.id}`, className: panelClassName, children: area.content }, area.id) : null)] });
56
+ }
package/dist/index.d.ts CHANGED
@@ -45,4 +45,9 @@ export * from './AnimalControllerAttestationPanel.js';
45
45
  export * from './EmergencyTaskHub.js';
46
46
  export * from './SubjectCommunicationCenter.js';
47
47
  export * from './ProfessionalAccessPanel.js';
48
+ export * from './NotificationComposer.js';
49
+ export * from './notification-bff-client.js';
50
+ export * from './CommunicationsWorkspace.js';
51
+ export * from './SelectedSubjectWorkspace.js';
52
+ export * from './SubjectWorkspace.js';
48
53
  //# 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"}
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"}
package/dist/index.js CHANGED
@@ -45,3 +45,8 @@ export * from './AnimalControllerAttestationPanel.js';
45
45
  export * from './EmergencyTaskHub.js';
46
46
  export * from './SubjectCommunicationCenter.js';
47
47
  export * from './ProfessionalAccessPanel.js';
48
+ export * from './NotificationComposer.js';
49
+ export * from './notification-bff-client.js';
50
+ export * from './CommunicationsWorkspace.js';
51
+ export * from './SelectedSubjectWorkspace.js';
52
+ export * from './SubjectWorkspace.js';
@@ -0,0 +1,44 @@
1
+ import { type NotificationAttachment, type NotificationComposerDraft, type PublicNotificationRecord } from 'sos-data-utils-ts/public-notifications';
2
+ type AuthorizedFetch = (url: string, init: RequestInit) => Promise<Response>;
3
+ export type NotificationSendResult = Readonly<{
4
+ notificationId: string;
5
+ status: 'accepted';
6
+ }>;
7
+ export type NotificationProfileProof = Readonly<{
8
+ profileId: string;
9
+ profileSessionToken: string;
10
+ }>;
11
+ export type PersonalNotificationContext = Readonly<{
12
+ subjectReference: string;
13
+ subjectKind: 'human';
14
+ }> | Readonly<{
15
+ subjectReference: string;
16
+ subjectKind: 'animal';
17
+ species: 'dog' | 'cat';
18
+ }>;
19
+ /** Personal public-topic reader; the BFF derives subscriptions, exclusions and delivery decisions. */
20
+ export declare function createPersonalNotificationBffClient(input: Readonly<{
21
+ authorizedFetch: AuthorizedFetch;
22
+ routeRoot?: '/api/notifications';
23
+ }>): Readonly<{
24
+ listPublic(context: PersonalNotificationContext): Promise<readonly PublicNotificationRecord[]>;
25
+ }>;
26
+ /** Shared transport adapter for browser, mobile-web and assistant hosts. */
27
+ export declare function createNotificationBffClient(input: Readonly<{
28
+ authorizedFetch: AuthorizedFetch;
29
+ routeRoot?: '/api/notifications';
30
+ }>): Readonly<{
31
+ uploadAttachment(file: File): Promise<NotificationAttachment>;
32
+ send(draft: NotificationComposerDraft): Promise<NotificationSendResult>;
33
+ }>;
34
+ /** Public-dataset transport shared by professional portals; the BFF validates the trusted profile proof. */
35
+ export declare function createPublicNotificationBffClient(input: Readonly<{
36
+ authorizedFetch: AuthorizedFetch;
37
+ profileProof: NotificationProfileProof;
38
+ routeRoot?: '/api/notifications/public';
39
+ }>): Readonly<{
40
+ uploadAttachment(file: File): Promise<NotificationAttachment>;
41
+ send(draft: NotificationComposerDraft): Promise<NotificationSendResult>;
42
+ }>;
43
+ export {};
44
+ //# sourceMappingURL=notification-bff-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-bff-client.d.ts","sourceRoot":"","sources":["../src/notification-bff-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC9B,MAAM,wCAAwC,CAAA;AAE/C,KAAK,eAAe,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAE5E,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,UAAU,CAAA;CACnB,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;CAC5B,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GACnC,QAAQ,CAAC;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,GAC5D,QAAQ,CAAC;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,KAAK,GAAG,KAAK,CAAA;CAAE,CAAC,CAAA;AAEzF,sGAAsG;AACtG,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,QAAQ,CAAC;IAClE,eAAe,EAAE,eAAe,CAAA;IAChC,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC,CAAC;wBAG4B,2BAA2B,GAAG,OAAO,CAAC,SAAS,wBAAwB,EAAE,CAAC;GAavG;AAED,4EAA4E;AAC5E,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,CAAC;IAC1D,eAAe,EAAE,eAAe,CAAA;IAChC,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC,CAAC;2BAI+B,IAAI,GAAG,OAAO,CAAC,sBAAsB,CAAC;gBAYjD,yBAAyB,GAAG,OAAO,CAAC,sBAAsB,CAAC;GAchF;AAED,4GAA4G;AAC5G,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,QAAQ,CAAC;IAChE,eAAe,EAAE,eAAe,CAAA;IAChC,YAAY,EAAE,wBAAwB,CAAA;IACtC,SAAS,CAAC,EAAE,2BAA2B,CAAA;CACxC,CAAC;2BAQ+B,IAAI,GAAG,OAAO,CAAC,sBAAsB,CAAC;gBAWjD,yBAAyB,GAAG,OAAO,CAAC,sBAAsB,CAAC;GAchF"}
@@ -0,0 +1,88 @@
1
+ import { normalizePublicNotificationRecord, normalizeNotificationComposerDraft, } from 'sos-data-utils-ts/public-notifications';
2
+ /** Personal public-topic reader; the BFF derives subscriptions, exclusions and delivery decisions. */
3
+ export function createPersonalNotificationBffClient(input) {
4
+ const routeRoot = input.routeRoot ?? '/api/notifications';
5
+ return Object.freeze({
6
+ async listPublic(context) {
7
+ const subjectReference = context.subjectReference.trim();
8
+ if (!subjectReference)
9
+ throw new Error('notification_subject_reference_required');
10
+ const query = new URLSearchParams({ subject: subjectReference });
11
+ if (context.subjectKind === 'animal')
12
+ query.set('species', context.species);
13
+ const response = await input.authorizedFetch(`${routeRoot}?${query.toString()}`, { method: 'GET' });
14
+ const payload = await response.json();
15
+ if (!response.ok || !Array.isArray(payload.notifications)) {
16
+ throw new Error(payload.error || 'notification_list_failed');
17
+ }
18
+ return Object.freeze(payload.notifications.map(normalizePublicNotificationRecord));
19
+ },
20
+ });
21
+ }
22
+ /** Shared transport adapter for browser, mobile-web and assistant hosts. */
23
+ export function createNotificationBffClient(input) {
24
+ const routeRoot = input.routeRoot ?? '/api/notifications';
25
+ return Object.freeze({
26
+ async uploadAttachment(file) {
27
+ const form = new FormData();
28
+ form.set('attachment', file);
29
+ const response = await input.authorizedFetch(`${routeRoot}/attachments`, {
30
+ method: 'POST',
31
+ body: form,
32
+ });
33
+ const payload = await response.json();
34
+ if (!response.ok || !payload.attachment)
35
+ throw new Error(payload.error || 'notification_attachment_upload_failed');
36
+ return Object.freeze(payload.attachment);
37
+ },
38
+ async send(draft) {
39
+ const normalizedDraft = normalizeNotificationComposerDraft(draft);
40
+ const response = await input.authorizedFetch(routeRoot, {
41
+ method: 'POST',
42
+ headers: { 'content-type': 'application/json' },
43
+ body: JSON.stringify({ operation: 'send', draft: normalizedDraft }),
44
+ });
45
+ const payload = await response.json();
46
+ if (!response.ok || !payload.notificationId || payload.status !== 'accepted') {
47
+ throw new Error(payload.error || 'notification_send_failed');
48
+ }
49
+ return Object.freeze({ notificationId: payload.notificationId, status: 'accepted' });
50
+ },
51
+ });
52
+ }
53
+ /** Public-dataset transport shared by professional portals; the BFF validates the trusted profile proof. */
54
+ export function createPublicNotificationBffClient(input) {
55
+ const routeRoot = input.routeRoot ?? '/api/notifications/public';
56
+ const profileId = input.profileProof.profileId.trim();
57
+ const profileSessionToken = input.profileProof.profileSessionToken.trim();
58
+ if (!profileId)
59
+ throw new Error('profile_id_required');
60
+ if (!profileSessionToken)
61
+ throw new Error('wallet_session_required');
62
+ return Object.freeze({
63
+ async uploadAttachment(file) {
64
+ const form = new FormData();
65
+ form.set('profileId', profileId);
66
+ form.set('profileSessionToken', profileSessionToken);
67
+ form.set('file', file);
68
+ const response = await input.authorizedFetch(`${routeRoot}/attachments`, { method: 'POST', body: form });
69
+ const payload = await response.json();
70
+ if (!response.ok || !payload.attachment)
71
+ throw new Error(payload.error || 'notification_attachment_upload_failed');
72
+ return Object.freeze(payload.attachment);
73
+ },
74
+ async send(draft) {
75
+ const normalizedDraft = normalizeNotificationComposerDraft(draft);
76
+ const response = await input.authorizedFetch(routeRoot, {
77
+ method: 'POST',
78
+ headers: { 'content-type': 'application/json' },
79
+ body: JSON.stringify({ operation: 'send', profileId, profileSessionToken, draft: normalizedDraft }),
80
+ });
81
+ const payload = await response.json();
82
+ if (!response.ok || !payload.notificationId || payload.status !== 'accepted') {
83
+ throw new Error(payload.error || 'public_notification_send_failed');
84
+ }
85
+ return Object.freeze({ notificationId: payload.notificationId, status: 'accepted' });
86
+ },
87
+ });
88
+ }
package/dist/styles.css CHANGED
@@ -772,6 +772,7 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
772
772
  @media (max-width: 620px) { .uhcp-emergency-copy > header { display: grid; } .uhcp-emergency-copy-legend { width: 100%; } }
773
773
  .uhcp-settings{max-width:72rem;margin:0 auto;padding:2rem 1rem 4rem;display:grid;gap:1rem}.uhcp-settings>header,.uhcp-settings>section{background:var(--uhcp-surface,#fff);border:1px solid var(--uhcp-border,#dbe4ea);border-radius:1rem;padding:1.25rem}.uhcp-settings h1,.uhcp-settings h2{margin-top:0}.uhcp-settings fieldset{border:0;padding:0;margin:1rem 0;display:flex;gap:.5rem;flex-wrap:wrap}.uhcp-settings legend{width:100%;font-weight:700;margin-bottom:.5rem}.uhcp-settings button,.uhcp-settings-links a{border:1px solid var(--uhcp-border,#c9d7df);border-radius:.7rem;background:transparent;color:inherit;padding:.65rem .85rem;text-decoration:none}.uhcp-settings button[aria-pressed=true]{background:var(--uhcp-accent,#dceff7);border-color:var(--uhcp-accent-strong,#2489a7)}.uhcp-settings-links{display:flex;gap:.6rem;flex-wrap:wrap}
774
774
  .uhcp-legal-document{color:var(--uhcp-text);line-height:1.7}.uhcp-legal-document h2{font-size:clamp(1.6rem,4vw,2.35rem);line-height:1.15;margin:0 0 1.25rem}.uhcp-legal-document h3{font-size:1.2rem;line-height:1.3;margin:2rem 0 .65rem}.uhcp-legal-document p{margin:0 0 1rem}.uhcp-legal-document ul{margin:0 0 1rem;padding-left:1.4rem}.uhcp-legal-document code{background:var(--uhcp-panel-muted);border-radius:.3rem;padding:.08rem .3rem}
775
+ .uhcp-notification-composer{background:var(--uhcp-panel);border:1px solid var(--uhcp-border);border-radius:1.5rem;color:var(--uhcp-text);display:grid;gap:.85rem;padding:1.25rem}.uhcp-notification-composer h2,.uhcp-notification-composer h3,.uhcp-notification-composer p{margin:0}.uhcp-notification-composer label,.uhcp-notification-composer legend{font-weight:800}.uhcp-notification-composer input,.uhcp-notification-composer select,.uhcp-notification-composer textarea,.uhcp-notification-composer button{font:inherit}.uhcp-notification-composer>input,.uhcp-notification-composer>select,.uhcp-notification-composer textarea{background:var(--uhcp-panel);border:1px solid var(--uhcp-border);border-radius:.75rem;color:inherit;padding:.75rem}.uhcp-notification-composer textarea{min-height:12rem;resize:vertical;width:100%}.uhcp-notification-composer-body{display:grid;gap:1rem;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.uhcp-notification-composer-body>div{display:grid;gap:.45rem}.uhcp-notification-composer-body>section{background:var(--uhcp-panel-muted);border:1px solid var(--uhcp-border);border-radius:1rem;min-width:0;padding:1rem}.uhcp-notification-composer-body>section h3{font-size:.85rem;margin-bottom:.75rem;text-transform:uppercase}.uhcp-notification-composer fieldset{border:1px solid var(--uhcp-border);border-radius:1rem;display:grid;gap:.75rem;margin:0;padding:1rem}.uhcp-notification-composer fieldset ul{display:grid;gap:.5rem;list-style:none;margin:0;padding:0}.uhcp-notification-composer fieldset li{align-items:center;background:var(--uhcp-panel-muted);border-radius:.7rem;display:flex;gap:.75rem;justify-content:space-between;padding:.6rem .75rem}.uhcp-notification-composer button{border:0;border-radius:.75rem;cursor:pointer;font-weight:800;padding:.7rem 1rem}.uhcp-notification-composer>button{background:var(--uhcp-turquoise);color:#fff;justify-self:start}.uhcp-notification-composer [role='alert']{color:#c64747;font-weight:800}.uhcp-notification-composer [role='status']{color:var(--uhcp-emerald);font-weight:800}@media(max-width:720px){.uhcp-notification-composer-body{grid-template-columns:1fr}}
775
776
  .uhcp-consent-directory{background:var(--uhcp-panel);border:1px solid var(--uhcp-border);border-radius:1.5rem;color:var(--uhcp-text);display:grid;gap:1rem;padding:1.25rem}.uhcp-consent-directory>header{align-items:flex-start;display:flex;gap:1rem;justify-content:space-between}.uhcp-consent-directory h2,.uhcp-consent-directory p{margin:0}.uhcp-consent-directory>header p,.uhcp-consent-list article>p{color:var(--uhcp-muted)}.uhcp-consent-directory button,.uhcp-consent-directory select{background:var(--uhcp-panel);border:1px solid var(--uhcp-border);border-radius:.7rem;color:inherit;font:inherit;padding:.65rem .85rem}.uhcp-consent-list{display:grid;gap:.75rem}.uhcp-consent-list article{background:var(--uhcp-panel-muted);border:1px solid var(--uhcp-border);border-radius:1rem;display:grid;gap:.55rem;padding:1rem}.uhcp-consent-list article>div:first-child{align-items:center;display:flex;flex-wrap:wrap;gap:.55rem}.uhcp-consent-list article span{background:color-mix(in srgb,var(--uhcp-turquoise) 13%,transparent);border-radius:999px;font-size:.8rem;padding:.25rem .5rem}.uhcp-consent-actions{display:flex;flex-wrap:wrap;gap:.5rem}.uhcp-consent-editor{border-top:1px solid var(--uhcp-border);display:grid;gap:1rem;padding-top:1rem}.uhcp-consent-editor>label{display:grid;gap:.35rem}.uhcp-consent-editor fieldset{border:1px solid var(--uhcp-border);border-radius:1rem;display:flex;flex-wrap:wrap;gap:.75rem;margin:0;padding:1rem}.uhcp-consent-editor legend{font-weight:800;padding:0 .35rem}.uhcp-consent-editor fieldset label{align-items:center;display:flex;gap:.35rem}.uhcp-consent-editor>div{display:flex;gap:.6rem}@media(max-width:620px){.uhcp-consent-directory>header{display:grid}.uhcp-consent-editor fieldset{display:grid}}
776
777
  .uhcp-loading-skeleton{background:var(--uhcp-panel);border:1px solid var(--uhcp-border);border-radius:2rem;display:grid;gap:1rem;margin:0 auto;max-width:1160px;overflow:hidden;padding:clamp(1.25rem,4vw,2.5rem);position:relative}.uhcp-loading-skeleton::after{animation:uhcp-loading-sweep 1.45s ease-in-out infinite;background:linear-gradient(90deg,transparent,color-mix(in srgb,#fff 42%,transparent),transparent);content:"";inset:0;position:absolute;transform:translateX(-100%)}.uhcp-loading-label{height:1px;overflow:hidden;position:absolute;width:1px;clip-path:inset(50%)}.uhcp-loading-heading,.uhcp-loading-copy,.uhcp-loading-row span{animation:uhcp-loading-pulse 1.45s ease-in-out infinite;background:color-mix(in srgb,var(--uhcp-muted) 17%,var(--uhcp-panel-muted));border-radius:999px}.uhcp-loading-heading{height:2.25rem;max-width:23rem;width:62%}.uhcp-loading-copy{height:1rem;max-width:38rem;width:86%}.uhcp-loading-grid{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));margin-top:.5rem}.uhcp-loading-row{background:var(--uhcp-panel-muted);border:1px solid var(--uhcp-border);border-radius:1.35rem;display:grid;gap:.8rem;padding:1.25rem}.uhcp-loading-row span{display:block;height:.8rem}.uhcp-loading-row span:nth-child(1){width:35%}.uhcp-loading-row span:nth-child(2){height:1.2rem;width:72%}.uhcp-loading-row span:nth-child(3){width:90%}.uhcp-loading-skeleton-compact{border-radius:1.25rem;padding:1.25rem}.uhcp-loading-skeleton-compact .uhcp-loading-heading{height:1.5rem}@keyframes uhcp-loading-pulse{0%,100%{opacity:.55}50%{opacity:1}}@keyframes uhcp-loading-sweep{to{transform:translateX(100%)}}@media(prefers-reduced-motion:reduce){.uhcp-loading-skeleton::after,.uhcp-loading-heading,.uhcp-loading-copy,.uhcp-loading-row span{animation:none}}
777
778
  .uhcp-animal-onboarding { display: grid; gap: 1.25rem; }
@@ -936,3 +937,39 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
936
937
  .uhcp-auth-progress { align-items: center; display: flex; font-weight: 750; gap: .6rem; }
937
938
  .uhcp-auth-progress > span { animation: uhcp-auth-spin .8s linear infinite; border: 3px solid var(--uhcp-line, #cbd5e1); border-radius: 50%; border-top-color: var(--uhcp-action-end, #2563a9); height: 1rem; width: 1rem; }
938
939
  @keyframes uhcp-auth-spin { to { transform: rotate(360deg); } }
940
+ .uhcp-communications-tabs,
941
+ .uhcp-communications-topic-tabs {
942
+ display: flex;
943
+ flex-wrap: wrap;
944
+ gap: 0.75rem;
945
+ margin-bottom: 1rem;
946
+ }
947
+
948
+ .uhcp-communications-tabs button,
949
+ .uhcp-communications-topic-tabs button {
950
+ min-height: 2.75rem;
951
+ border: 1px solid var(--uhcp-border, #cbd5e1);
952
+ border-radius: 999px;
953
+ padding: 0.625rem 1rem;
954
+ background: var(--uhcp-surface, #fff);
955
+ color: inherit;
956
+ font: inherit;
957
+ font-weight: 700;
958
+ }
959
+
960
+ .uhcp-communications-tabs button[aria-selected='true'],
961
+ .uhcp-communications-topic-tabs button[aria-selected='true'] {
962
+ border-color: var(--uhcp-accent, #0f766e);
963
+ background: color-mix(in srgb, var(--uhcp-accent, #0f766e) 12%, transparent);
964
+ }
965
+
966
+ .uhcp-communications-topic-tabs button:disabled {
967
+ cursor: not-allowed;
968
+ opacity: 0.55;
969
+ }
970
+
971
+ .uhcp-selected-subject-workspace { display: grid; gap: 1rem; }
972
+ .uhcp-selected-subject-workspace > header { display: flex; align-items: start; justify-content: space-between; gap: 1rem; }
973
+ .uhcp-selected-subject-tabs { display: flex; flex-wrap: wrap; gap: 0.75rem; }
974
+ .uhcp-selected-subject-tabs button { min-height: 2.75rem; border: 1px solid var(--uhcp-border, #cbd5e1); border-radius: 999px; padding: 0.625rem 1rem; background: var(--uhcp-surface, #fff); color: inherit; font: inherit; font-weight: 700; }
975
+ .uhcp-selected-subject-tabs button[aria-selected='true'] { border-color: var(--uhcp-accent, #0f766e); background: color-mix(in srgb, var(--uhcp-accent, #0f766e) 12%, transparent); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
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",
@@ -31,9 +31,9 @@
31
31
  "react-dom": ">=18.3.0"
32
32
  },
33
33
  "dependencies": {
34
- "fhir-data-utils-ts": "0.6.18",
34
+ "fhir-data-utils-ts": "0.6.21",
35
35
  "gdc-common-utils-ts": "2.9.30",
36
- "sos-data-utils-ts": "0.6.3",
36
+ "sos-data-utils-ts": "0.6.15",
37
37
  "terminology-components-react": "0.2.0"
38
38
  },
39
39
  "devDependencies": {