@mindbill/react 0.12.0 → 0.13.1

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/README.md CHANGED
@@ -10,6 +10,8 @@ npm install @mindbill/react @mindbill/node
10
10
 
11
11
  `ConnectedBillLifecycle` is the default integration. It owns bill loading, payer lookup, editable review, attachments, submission, status refresh, EORs, payment posting, Second Bill Review, correction/resubmission, and close.
12
12
 
13
+ Procedure entry is keyboard-first: the form always keeps one empty row after the entered lines, grows as soon as that row is partially filled, and omits the empty row from save and submit payloads.
14
+
13
15
  ```tsx
14
16
  import { ConnectedBillLifecycle } from "@mindbill/react";
15
17
 
@@ -37,7 +39,7 @@ Choose a complete preset, then override only the tokens your design system owns.
37
39
  />
38
40
  ```
39
41
 
40
- Available presets are `mindbill`, `qme-companion`, and `orange-bright`. Preset names describe visual styles rather than customer or partner brands. Supported overrides include accent, accent text, background, surface, input background, text, muted text, border, font, panel radius, control radius, shadow, danger, success, and warning colors.
42
+ Available presets are `mindbill`, `qme-companion`, `orange-bright`, and `clinical-blue`. Preset names describe visual styles rather than customer or partner brands. Supported overrides include accent, accent text, background, surface, input background, text, muted text, border, font, panel radius, control radius, shadow, danger, success, and warning colors.
41
43
 
42
44
  Add one authenticated route to your app. It verifies that the signed-in user may access the bill, then mints an exact-origin, bill-scoped token. The Partner API key stays on the server.
43
45
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { MindBillAppearance, MindBillEventDetail, MindBillErrorDetail } from '@mindbill/embed';
2
2
  export { MindBillAppearance, MindBillErrorDetail, MindBillEventDetail } from '@mindbill/embed';
3
3
  import { CSSProperties, ReactElement, ReactNode } from 'react';
4
+ import { BillLifecycleClientOptions, BillLifecycleData, BillLifecycleClient, BillEorDocument } from '@mindbill/browser';
5
+ export { BillEorDocument, BillLifecycleAction, BillLifecycleActionId, BillLifecycleClient, BillLifecycleClientOptions, BillLifecycleData, BillLifecycleSession, BillLifecycleSessionProvider, BillLifecycleSessionRequest, CloseBillInput, PostBillPaymentInput, SubmitSecondReviewInput, createBillLifecycleClient } from '@mindbill/browser';
4
6
 
5
- type MindBillThemePreset = "mindbill" | "qme-companion" | "orange-bright";
7
+ type MindBillThemePreset = "mindbill" | "qme-companion" | "orange-bright" | "clinical-blue";
6
8
  type MindBillReactAppearance = MindBillAppearance & {
7
9
  /** A complete starting point. Individual tokens below always win. */
8
10
  preset?: MindBillThemePreset;
@@ -65,6 +67,23 @@ declare const mindBillThemePresets: {
65
67
  readonly warningColor: "#9a5b13";
66
68
  readonly fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif";
67
69
  };
70
+ readonly "clinical-blue": {
71
+ readonly accentColor: "#1677ff";
72
+ readonly accentTextColor: "#ffffff";
73
+ readonly backgroundColor: "#f5f7fa";
74
+ readonly surfaceColor: "#ffffff";
75
+ readonly inputBackgroundColor: "#ffffff";
76
+ readonly textColor: "#1f2d3d";
77
+ readonly mutedColor: "#66788a";
78
+ readonly borderColor: "#d9e2ec";
79
+ readonly borderRadius: "8px";
80
+ readonly controlRadius: "6px";
81
+ readonly shadow: "0 2px 8px rgba(31,45,61,.06)";
82
+ readonly dangerColor: "#d4380d";
83
+ readonly successColor: "#389e0d";
84
+ readonly warningColor: "#d48806";
85
+ readonly fontFamily: "Inter, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif";
86
+ };
68
87
  };
69
88
  declare function resolveMindBillAppearance(appearance: MindBillReactAppearance | undefined): MindBillReactAppearance;
70
89
  declare function mindBillAppearanceStyle(appearance: MindBillReactAppearance | undefined, style?: CSSProperties): CSSProperties;
@@ -115,6 +134,8 @@ type BillReviewLineItem = {
115
134
  charge: number;
116
135
  feeSchedule?: number;
117
136
  };
137
+ /** Keeps completed/partial lines plus one keyboard-ready empty row. */
138
+ declare function ensureTrailingProcedureLine(lines: readonly BillReviewLineItem[]): BillReviewLineItem[];
118
139
  type BillReviewAttachment = {
119
140
  id: string;
120
141
  filename: string;
@@ -341,87 +362,6 @@ type ConnectedBillStatusProps = UseBillStatusOptions & {
341
362
  };
342
363
  declare function ConnectedBillStatus({ actions, appearance, className, style, loadingFallback, errorFallback, ...options }: ConnectedBillStatusProps): ReactElement | null;
343
364
 
344
- type BillLifecycleActionId = "edit_and_submit" | "correct_and_resubmit" | "second_review" | "independent_bill_review" | "view_eor" | "post_payment" | "close";
345
- type BillLifecycleAction = {
346
- id: BillLifecycleActionId;
347
- label: string;
348
- enabled: boolean;
349
- primary?: boolean;
350
- reason?: string;
351
- };
352
- type BillEorDocument = {
353
- id: string;
354
- filename: string;
355
- description: string | null;
356
- addedAt: string;
357
- contentUrl: string;
358
- };
359
- type BillLifecycleData = BillReviewData & {
360
- lifecycle: {
361
- state: string;
362
- nativeStatus: string;
363
- submittedAt?: string | null;
364
- agingDays?: number | null;
365
- updatedAt?: string | null;
366
- actions: BillLifecycleAction[];
367
- };
368
- eors: BillEorDocument[];
369
- };
370
- type BillLifecycleSession = {
371
- token: string;
372
- expiresAt?: string;
373
- apiBaseUrl?: string;
374
- };
375
- type BillLifecycleSessionRequest = {
376
- billId: string;
377
- component: "bill-review";
378
- signal: AbortSignal;
379
- };
380
- type BillLifecycleSessionProvider = (request: BillLifecycleSessionRequest) => Promise<BillLifecycleSession>;
381
- type CloseBillInput = {
382
- reason: string;
383
- };
384
- type PostBillPaymentInput = {
385
- amount: number;
386
- method: "check" | "eft";
387
- checkNumber?: string;
388
- depositDate: string;
389
- note?: string;
390
- };
391
- type SubmitSecondReviewInput = {
392
- reason: string;
393
- payerClaimControlNumber: string;
394
- disputedAmount: number | undefined;
395
- attachmentIds: string[];
396
- route: BillSubmissionRoute;
397
- };
398
- type BillLifecycleClientOptions = {
399
- billId: string;
400
- /** Same-origin server route that mints a short-lived, bill-scoped session. */
401
- sessionEndpoint?: string | undefined;
402
- /** Advanced escape hatch for a custom session exchange. */
403
- getSession?: BillLifecycleSessionProvider | undefined;
404
- apiBaseUrl?: string | undefined;
405
- fetch?: typeof globalThis.fetch | undefined;
406
- };
407
- type BillLifecycleClient = {
408
- getLifecycle: (signal?: AbortSignal) => Promise<BillLifecycleData>;
409
- searchClaimsAdministrators: (query: string, claimNumber?: string) => Promise<BillReviewPayer[]>;
410
- saveReview: (input: BillReviewSaveInput) => Promise<BillLifecycleData>;
411
- submitBill: (input: BillReviewSaveInput, route: BillSubmissionRoute) => Promise<BillLifecycleData>;
412
- addAttachment: (file: File, documentType: BillReviewDocumentType, description?: string) => Promise<BillLifecycleData>;
413
- removeAttachment: (attachmentId: string) => Promise<BillLifecycleData>;
414
- getAttachment: (attachmentId: string) => Promise<Blob>;
415
- getEor: (documentId: string) => Promise<Blob>;
416
- closeBill: (input: CloseBillInput) => Promise<BillLifecycleData>;
417
- postPayment: (input: PostBillPaymentInput) => Promise<BillLifecycleData>;
418
- submitSecondReview: (input: SubmitSecondReviewInput) => Promise<BillLifecycleData>;
419
- startCorrection: () => Promise<{
420
- replacementBillId: string;
421
- data: BillLifecycleData;
422
- }>;
423
- clearSession: () => void;
424
- };
425
365
  type UseBillLifecycleOptions = BillLifecycleClientOptions & {
426
366
  refreshInterval?: number;
427
367
  enabled?: boolean;
@@ -448,12 +388,6 @@ type UseBillLifecycleResult = {
448
388
  submitSecondReview: BillLifecycleClient["submitSecondReview"];
449
389
  startCorrection: () => Promise<BillLifecycleData>;
450
390
  };
451
- /**
452
- * Browser-safe client for the complete bill lifecycle. The only long-lived
453
- * secret remains on the partner server; this client uses a short-lived,
454
- * origin-bound session scoped to one bill.
455
- */
456
- declare function createBillLifecycleClient({ billId, sessionEndpoint, getSession, apiBaseUrl, fetch: fetchOverride, }: BillLifecycleClientOptions): BillLifecycleClient;
457
391
  declare function useBillLifecycle({ billId: providedBillId, sessionEndpoint, getSession, apiBaseUrl, refreshInterval, enabled, initialData, onBillIdChange, fetch: fetchOverride, }: UseBillLifecycleOptions): UseBillLifecycleResult;
458
392
  type ConnectedBillLifecycleProps = UseBillLifecycleOptions & {
459
393
  appearance?: MindBillReactAppearance;
@@ -478,4 +412,4 @@ type MindBillWidgetProps = {
478
412
  declare function MindBillBillTimeline(props: MindBillWidgetProps): ReactElement;
479
413
  declare function MindBillBillReview(props: MindBillWidgetProps): ReactElement;
480
414
 
481
- export { type BillEorDocument, type BillLifecycleAction, type BillLifecycleActionId, type BillLifecycleClient, type BillLifecycleClientOptions, type BillLifecycleData, type BillLifecycleSession, type BillLifecycleSessionProvider, type BillLifecycleSessionRequest, type BillReviewAttachment, type BillReviewBillingProvider, type BillReviewClinician, type BillReviewData, type BillReviewDocumentType, type BillReviewDraft, type BillReviewFeatures, BillReviewForm, type BillReviewFormProps, type BillReviewLineItem, type BillReviewLocation, type BillReviewPayer, type BillReviewSaveInput, type BillStatusAction, type BillStatusClient, type BillStatusClientOptions, type BillStatusData, type BillStatusSession, type BillStatusSessionProvider, type BillStatusSessionRequest, BillStatusSummary, type BillStatusSummaryProps, type BillSubmissionRoute, type CloseBillInput, ConnectedBillLifecycle, type ConnectedBillLifecycleProps, ConnectedBillStatus, type ConnectedBillStatusProps, MindBillBillReview, MindBillBillTimeline, type MindBillReactAppearance, type MindBillThemePreset, type MindBillWidgetProps, type PostBillPaymentInput, type SubmitSecondReviewInput, type UseBillLifecycleOptions, type UseBillLifecycleResult, type UseBillStatusOptions, type UseBillStatusResult, buildBillReviewSaveInput, createBillLifecycleClient, createBillStatusClient, mindBillAppearanceStyle, mindBillThemePresets, resolveMindBillAppearance, useBillLifecycle, useBillStatus };
415
+ export { type BillReviewAttachment, type BillReviewBillingProvider, type BillReviewClinician, type BillReviewData, type BillReviewDocumentType, type BillReviewDraft, type BillReviewFeatures, BillReviewForm, type BillReviewFormProps, type BillReviewLineItem, type BillReviewLocation, type BillReviewPayer, type BillReviewSaveInput, type BillStatusAction, type BillStatusClient, type BillStatusClientOptions, type BillStatusData, type BillStatusSession, type BillStatusSessionProvider, type BillStatusSessionRequest, BillStatusSummary, type BillStatusSummaryProps, type BillSubmissionRoute, ConnectedBillLifecycle, type ConnectedBillLifecycleProps, ConnectedBillStatus, type ConnectedBillStatusProps, MindBillBillReview, MindBillBillTimeline, type MindBillReactAppearance, type MindBillThemePreset, type MindBillWidgetProps, type UseBillLifecycleOptions, type UseBillLifecycleResult, type UseBillStatusOptions, type UseBillStatusResult, buildBillReviewSaveInput, createBillStatusClient, ensureTrailingProcedureLine, mindBillAppearanceStyle, mindBillThemePresets, resolveMindBillAppearance, useBillLifecycle, useBillStatus };
package/dist/index.js CHANGED
@@ -55,6 +55,23 @@ var mindBillThemePresets = {
55
55
  successColor: "#16794f",
56
56
  warningColor: "#9a5b13",
57
57
  fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif"
58
+ },
59
+ "clinical-blue": {
60
+ accentColor: "#1677ff",
61
+ accentTextColor: "#ffffff",
62
+ backgroundColor: "#f5f7fa",
63
+ surfaceColor: "#ffffff",
64
+ inputBackgroundColor: "#ffffff",
65
+ textColor: "#1f2d3d",
66
+ mutedColor: "#66788a",
67
+ borderColor: "#d9e2ec",
68
+ borderRadius: "8px",
69
+ controlRadius: "6px",
70
+ shadow: "0 2px 8px rgba(31,45,61,.06)",
71
+ dangerColor: "#d4380d",
72
+ successColor: "#389e0d",
73
+ warningColor: "#d48806",
74
+ fontFamily: "Inter, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
58
75
  }
59
76
  };
60
77
  function resolveMindBillAppearance(appearance) {
@@ -86,6 +103,19 @@ function mindBillAppearanceStyle(appearance, style) {
86
103
  // src/native-bill-review.tsx
87
104
  import { useEffect, useId, useMemo, useRef, useState } from "react";
88
105
  import { jsx, jsxs } from "react/jsx-runtime";
106
+ var EMPTY_PROCEDURE_LINE = {
107
+ code: "",
108
+ modifiers: [],
109
+ units: 1,
110
+ charge: 0
111
+ };
112
+ function isEmptyProcedureLine(line) {
113
+ return !line.code.trim() && line.modifiers.length === 0 && line.units === 1;
114
+ }
115
+ function ensureTrailingProcedureLine(lines) {
116
+ const entered = lines.filter((line) => !isEmptyProcedureLine(line)).map((line) => ({ ...line, modifiers: [...line.modifiers] }));
117
+ return entered.length < 50 ? [...entered, { ...EMPTY_PROCEDURE_LINE, modifiers: [] }] : entered;
118
+ }
89
119
  var EMPTY_BILLING_PROVIDER = {
90
120
  name: "",
91
121
  taxId: "",
@@ -170,7 +200,7 @@ function toDraft(data) {
170
200
  },
171
201
  clinician: { ...EMPTY_CLINICIAN, ...snapshot?.renderingProvider ?? {} },
172
202
  location: { ...EMPTY_LOCATION, ...snapshot?.placeOfService ?? {} },
173
- lineItems: data.bill.lineItems.map((line) => ({ ...line }))
203
+ lineItems: ensureTrailingProcedureLine(data.bill.lineItems)
174
204
  };
175
205
  }
176
206
  function buildBillReviewSaveInput(draft) {
@@ -685,15 +715,12 @@ function BillReviewForm({
685
715
  ] })
686
716
  ] }),
687
717
  /* @__PURE__ */ jsxs("section", { className: sectionClass, children: [
688
- /* @__PURE__ */ jsxs("div", { className: "mb-native-section-head", children: [
689
- /* @__PURE__ */ jsxs("div", { children: [
690
- /* @__PURE__ */ jsx("h3", { children: "Procedure lines" }),
691
- /* @__PURE__ */ jsx("p", { children: "Choose a billing profile, then review the codes and modifiers. Allowed amounts recalculate on save." })
692
- ] }),
693
- /* @__PURE__ */ jsx("button", { type: "button", className: "mb-native-button quiet", disabled: !editable || draft.lineItems.length >= 50, onClick: () => setDraft((current) => ({ ...current, lineItems: [...current.lineItems, { code: "", modifiers: [], units: 1, charge: 0 }] })), children: "+ Add line" })
694
- ] }),
718
+ /* @__PURE__ */ jsx("div", { className: "mb-native-section-head", children: /* @__PURE__ */ jsxs("div", { children: [
719
+ /* @__PURE__ */ jsx("h3", { children: "Procedure lines" }),
720
+ /* @__PURE__ */ jsx("p", { children: "Choose a billing profile, then review the codes and modifiers. A new row appears as you type; allowed amounts recalculate on save." })
721
+ ] }) }),
695
722
  features?.codingPresets === false ? null : /* @__PURE__ */ jsxs("div", { className: "mb-native-presets", role: "group", "aria-label": "Evaluation billing profile", children: [
696
- [["qme", "QME"], ["ame", "AME"], ["psych_qme", "Psych QME"]].map(([value, label]) => /* @__PURE__ */ jsx("button", { type: "button", className: inferPreset(draft.lineItems) === value ? "active" : "", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: applyPreset(current.lineItems, value) })), children: label }, value)),
723
+ [["qme", "QME"], ["ame", "AME"], ["psych_qme", "Psych QME"]].map(([value, label]) => /* @__PURE__ */ jsx("button", { type: "button", className: inferPreset(draft.lineItems) === value ? "active" : "", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(applyPreset(current.lineItems, value)) })), children: label }, value)),
697
724
  /* @__PURE__ */ jsx("span", { children: "Sets evaluator modifiers across med-legal lines. You can still edit each line." })
698
725
  ] }),
699
726
  /* @__PURE__ */ jsx("div", { className: "mb-native-lines", children: draft.lineItems.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "mb-native-line", children: [
@@ -710,15 +737,7 @@ function BillReviewForm({
710
737
  )[0];
711
738
  return { ...item, code, modifiers: presetLine?.modifiers || [] };
712
739
  });
713
- if (code && index === lineItems.length - 1 && lineItems.length < 50) {
714
- lineItems.push({
715
- code: "",
716
- modifiers: [],
717
- units: 1,
718
- charge: 0
719
- });
720
- }
721
- return { ...current, lineItems };
740
+ return { ...current, lineItems: ensureTrailingProcedureLine(lineItems) };
722
741
  });
723
742
  }, children: [
724
743
  line.code && !PROCEDURES.some(([code]) => code === line.code) ? /* @__PURE__ */ jsx("option", { value: line.code, children: line.code }) : null,
@@ -737,7 +756,7 @@ function BillReviewForm({
737
756
  /* @__PURE__ */ jsxs("select", { disabled: !editable, value: "", onChange: (event) => {
738
757
  const modifier = event.target.value;
739
758
  if (!modifier) return;
740
- setDraft((current) => ({ ...current, lineItems: current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: [.../* @__PURE__ */ new Set([...item.modifiers, modifier])] } : item) }));
759
+ setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: [.../* @__PURE__ */ new Set([...item.modifiers, modifier])] } : item)) }));
741
760
  }, children: [
742
761
  /* @__PURE__ */ jsx("option", { value: "", children: "Add modifier\u2026" }),
743
762
  MODIFIERS.filter(([value]) => !line.modifiers.includes(value) && modifierAppliesToCode(value, line.code)).map(([value, label]) => /* @__PURE__ */ jsxs("option", { value, children: [
@@ -748,7 +767,7 @@ function BillReviewForm({
748
767
  ] }, value))
749
768
  ] })
750
769
  ] }),
751
- /* @__PURE__ */ jsx("div", { className: "mb-native-chips", children: line.modifiers.map((modifier) => /* @__PURE__ */ jsxs("button", { type: "button", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: item.modifiers.filter((value) => value !== modifier) } : item) })), children: [
770
+ /* @__PURE__ */ jsx("div", { className: "mb-native-chips", children: line.modifiers.map((modifier) => /* @__PURE__ */ jsxs("button", { type: "button", disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, modifiers: item.modifiers.filter((value) => value !== modifier) } : item)) })), children: [
752
771
  "-",
753
772
  modifier,
754
773
  " \xD7"
@@ -756,13 +775,13 @@ function BillReviewForm({
756
775
  ] }),
757
776
  /* @__PURE__ */ jsxs("label", { className: "mb-native-field", children: [
758
777
  /* @__PURE__ */ jsx("span", { children: "Units" }),
759
- /* @__PURE__ */ jsx("input", { type: "number", min: "1", required: true, disabled: !editable, value: line.units, onChange: (event) => setDraft((current) => ({ ...current, lineItems: current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, units: Number(event.target.value) } : item) })) })
778
+ /* @__PURE__ */ jsx("input", { type: "number", min: "1", required: true, disabled: !editable, value: line.units, onChange: (event) => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.map((item, itemIndex) => itemIndex === index ? { ...item, units: Number(event.target.value) } : item)) })) })
760
779
  ] }),
761
780
  /* @__PURE__ */ jsxs("div", { className: "mb-native-allowed", children: [
762
781
  /* @__PURE__ */ jsx("span", { children: "Allowed" }),
763
782
  /* @__PURE__ */ jsx("strong", { children: money(line.charge) })
764
783
  ] }),
765
- /* @__PURE__ */ jsx("button", { type: "button", className: "mb-native-remove", "aria-label": `Remove ${line.code || "procedure"}`, disabled: !editable || draft.lineItems.length === 1, onClick: () => setDraft((current) => ({ ...current, lineItems: current.lineItems.filter((_, itemIndex) => itemIndex !== index) })), children: "\xD7" })
784
+ /* @__PURE__ */ jsx("button", { type: "button", className: "mb-native-remove", "aria-label": `Remove ${line.code || "procedure"}`, disabled: !editable, onClick: () => setDraft((current) => ({ ...current, lineItems: ensureTrailingProcedureLine(current.lineItems.filter((_, itemIndex) => itemIndex !== index)) })), children: "\xD7" })
766
785
  ] }, line.id || index)) })
767
786
  ] }),
768
787
  /* @__PURE__ */ jsxs("section", { className: sectionClass, children: [
@@ -1170,294 +1189,14 @@ import {
1170
1189
  useRef as useRef3,
1171
1190
  useState as useState3
1172
1191
  } from "react";
1192
+ import {
1193
+ DEFAULT_API_BASE_URL as DEFAULT_API_BASE_URL2,
1194
+ DEFAULT_SESSION_ENDPOINT as DEFAULT_SESSION_ENDPOINT2,
1195
+ createBillLifecycleClient
1196
+ } from "@mindbill/browser";
1197
+ import { createBillLifecycleClient as createBillLifecycleClient2 } from "@mindbill/browser";
1173
1198
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1174
- var DEFAULT_API_BASE_URL2 = "https://app.mindbill.org";
1175
- var DEFAULT_SESSION_ENDPOINT2 = "/api/mindbill/bill-session";
1176
1199
  var DEFAULT_REFRESH_INTERVAL2 = 6e4;
1177
- async function responseError2(response, fallback) {
1178
- const body = await response.json().catch(() => null);
1179
- const detail = body && typeof body === "object" ? body.detail ?? body.message ?? body.error : null;
1180
- return new Error(typeof detail === "string" ? detail : fallback);
1181
- }
1182
- function isSessionFresh2(session) {
1183
- if (!session) return false;
1184
- if (!session.expiresAt) return true;
1185
- const expiresAt = new Date(session.expiresAt).getTime();
1186
- return Number.isFinite(expiresAt) && expiresAt > Date.now() + 3e4;
1187
- }
1188
- function normalizeSession2(value) {
1189
- if (!value || typeof value !== "object") {
1190
- throw new Error("The billing session endpoint returned an invalid response.");
1191
- }
1192
- const candidate = value;
1193
- const nested = candidate.session ?? candidate.data;
1194
- const session = nested && typeof nested === "object" ? nested : candidate;
1195
- if (typeof session.token !== "string" || session.token.length < 8) {
1196
- throw new Error(
1197
- "The billing session endpoint did not return a browser session token."
1198
- );
1199
- }
1200
- return {
1201
- token: session.token,
1202
- ...typeof session.expiresAt === "string" ? { expiresAt: session.expiresAt } : {},
1203
- ...typeof session.apiBaseUrl === "string" ? { apiBaseUrl: session.apiBaseUrl } : {}
1204
- };
1205
- }
1206
- function normalizeLifecycle(value) {
1207
- if (!value || typeof value !== "object") {
1208
- throw new Error("The billing service returned an invalid bill lifecycle.");
1209
- }
1210
- const data = value;
1211
- if (!data.bill || !data.patient || !data.injury || !data.lifecycle || !Array.isArray(data.lifecycle.actions) || !Array.isArray(data.eors)) {
1212
- throw new Error("The billing service returned an invalid bill lifecycle.");
1213
- }
1214
- return data;
1215
- }
1216
- function idempotencyKey() {
1217
- if (typeof globalThis.crypto?.randomUUID === "function") {
1218
- return globalThis.crypto.randomUUID();
1219
- }
1220
- return `mb-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1221
- }
1222
- function createBillLifecycleClient({
1223
- billId,
1224
- sessionEndpoint = DEFAULT_SESSION_ENDPOINT2,
1225
- getSession,
1226
- apiBaseUrl = DEFAULT_API_BASE_URL2,
1227
- fetch: fetchOverride
1228
- }) {
1229
- const fetcher = fetchOverride ?? globalThis.fetch;
1230
- if (typeof fetcher !== "function") {
1231
- throw new Error("A Fetch API implementation is required.");
1232
- }
1233
- let session = null;
1234
- let sessionRequest = null;
1235
- const mintSession = async (signal, force = false) => {
1236
- if (!force && isSessionFresh2(session)) return session;
1237
- if (!force && sessionRequest) return sessionRequest;
1238
- const pending = getSession ? getSession({ billId, component: "bill-review", signal }) : fetcher(sessionEndpoint, {
1239
- method: "POST",
1240
- credentials: "same-origin",
1241
- headers: { "content-type": "application/json" },
1242
- body: JSON.stringify({ billId, component: "bill-review" }),
1243
- signal
1244
- }).then(async (response) => {
1245
- if (!response.ok) {
1246
- throw await responseError2(
1247
- response,
1248
- "The billing session could not be created."
1249
- );
1250
- }
1251
- return response.json();
1252
- });
1253
- const request2 = Promise.resolve(pending).then(normalizeSession2).then((nextSession) => {
1254
- session = nextSession;
1255
- return nextSession;
1256
- }).finally(() => {
1257
- if (sessionRequest === request2) sessionRequest = null;
1258
- });
1259
- sessionRequest = request2;
1260
- return request2;
1261
- };
1262
- const request = async (path, init = {}, providedSignal) => {
1263
- const controller = providedSignal ? null : new AbortController();
1264
- const signal = providedSignal ?? controller?.signal;
1265
- if (!signal) throw new Error("An AbortSignal could not be created.");
1266
- let browserSession = await mintSession(signal);
1267
- const perform = (current) => {
1268
- const base = (current.apiBaseUrl ?? apiBaseUrl).replace(/\/$/, "");
1269
- const headers = new Headers(init.headers);
1270
- headers.set("authorization", `Bearer ${current.token}`);
1271
- return fetcher(`${base}${path}`, { ...init, headers, signal });
1272
- };
1273
- let response = await perform(browserSession);
1274
- if (response.status === 401) {
1275
- session = null;
1276
- browserSession = await mintSession(signal, true);
1277
- response = await perform(browserSession);
1278
- }
1279
- return response;
1280
- };
1281
- const loadLifecycle = async (signal) => {
1282
- const response = await request("/partner/v2/browser/bill", {}, signal);
1283
- if (!response.ok) {
1284
- throw await responseError2(response, "Bill lifecycle could not be loaded.");
1285
- }
1286
- const body = await response.json();
1287
- return normalizeLifecycle(body.data);
1288
- };
1289
- const searchClaimsAdministrators = async (query, claimNumber) => {
1290
- const params = new URLSearchParams();
1291
- if (query.trim()) params.set("q", query.trim());
1292
- if (claimNumber?.trim()) params.set("claimNumber", claimNumber.trim());
1293
- const response = await request(
1294
- `/partner/v2/browser/claims-administrators?${params.toString()}`
1295
- );
1296
- if (!response.ok) {
1297
- throw await responseError2(
1298
- response,
1299
- "Claims administrator search is unavailable."
1300
- );
1301
- }
1302
- const body = await response.json();
1303
- if (!Array.isArray(body.results)) {
1304
- throw new Error("Claims administrator search returned an invalid response.");
1305
- }
1306
- return body.results.flatMap((value) => {
1307
- if (!value || typeof value !== "object") return [];
1308
- const payer = value;
1309
- if (typeof payer.id !== "string" || typeof payer.name !== "string") {
1310
- return [];
1311
- }
1312
- return [{
1313
- id: payer.id,
1314
- name: payer.name,
1315
- ...typeof payer.hasElectronic === "boolean" ? { hasElectronic: payer.hasElectronic } : {},
1316
- ...Array.isArray(payer.states) ? { states: payer.states.filter((state) => typeof state === "string") } : {},
1317
- ...["high", "medium", "directory"].includes(payer.confidence ?? "") ? {
1318
- confidence: payer.confidence
1319
- } : {},
1320
- ...typeof payer.recommended === "boolean" ? { recommended: payer.recommended } : {},
1321
- ...Array.isArray(payer.signals) ? {
1322
- signals: payer.signals.flatMap((signal) => {
1323
- if (!signal || typeof signal !== "object") return [];
1324
- const candidate = signal;
1325
- if (!["name", "claim_number"].includes(String(candidate.kind)) || !["match", "warning"].includes(String(candidate.state)) || typeof candidate.label !== "string") return [];
1326
- return [{
1327
- kind: candidate.kind,
1328
- state: candidate.state,
1329
- label: candidate.label
1330
- }];
1331
- })
1332
- } : {}
1333
- }];
1334
- });
1335
- };
1336
- const mutation = async (path, init, fallback) => {
1337
- const headers = new Headers(init.headers);
1338
- headers.set("idempotency-key", idempotencyKey());
1339
- const response = await request(path, { ...init, headers });
1340
- if (!response.ok) throw await responseError2(response, fallback);
1341
- return response;
1342
- };
1343
- const action = async (input, fallback) => {
1344
- const response = await mutation(
1345
- "/partner/v2/browser/actions",
1346
- {
1347
- method: "POST",
1348
- headers: { "content-type": "application/json" },
1349
- body: JSON.stringify(input)
1350
- },
1351
- fallback
1352
- );
1353
- const body = await response.json();
1354
- return normalizeLifecycle(body.data);
1355
- };
1356
- const saveReview = async (input) => {
1357
- await mutation(
1358
- "/partner/v2/browser/bill",
1359
- {
1360
- method: "PATCH",
1361
- headers: { "content-type": "application/json" },
1362
- body: JSON.stringify(input)
1363
- },
1364
- "Bill changes could not be saved."
1365
- );
1366
- return loadLifecycle();
1367
- };
1368
- return {
1369
- clearSession() {
1370
- session = null;
1371
- sessionRequest = null;
1372
- },
1373
- getLifecycle: loadLifecycle,
1374
- searchClaimsAdministrators,
1375
- saveReview,
1376
- async submitBill(input, route) {
1377
- await saveReview(input);
1378
- await mutation(
1379
- "/partner/v2/browser/submissions",
1380
- {
1381
- method: "POST",
1382
- headers: { "content-type": "application/json" },
1383
- body: JSON.stringify({ route })
1384
- },
1385
- "Bill could not be submitted."
1386
- );
1387
- return loadLifecycle();
1388
- },
1389
- async addAttachment(file, documentType, description) {
1390
- const body = new FormData();
1391
- body.set("file", file);
1392
- body.set("documentType", documentType);
1393
- if (description) body.set("description", description);
1394
- await mutation(
1395
- "/partner/v2/browser/documents",
1396
- { method: "POST", body },
1397
- "Document could not be attached."
1398
- );
1399
- return loadLifecycle();
1400
- },
1401
- async removeAttachment(attachmentId) {
1402
- await mutation(
1403
- `/partner/v2/browser/documents/${encodeURIComponent(attachmentId)}`,
1404
- { method: "DELETE" },
1405
- "Document could not be removed."
1406
- );
1407
- return loadLifecycle();
1408
- },
1409
- async getAttachment(attachmentId) {
1410
- const response = await request(
1411
- `/partner/v2/browser/documents/${encodeURIComponent(attachmentId)}`
1412
- );
1413
- if (!response.ok) {
1414
- throw await responseError2(response, "Document could not be opened.");
1415
- }
1416
- return response.blob();
1417
- },
1418
- async getEor(documentId) {
1419
- const response = await request(
1420
- `/partner/v2/browser/eors/${encodeURIComponent(documentId)}`
1421
- );
1422
- if (!response.ok) throw await responseError2(response, "EOR could not be opened.");
1423
- return response.blob();
1424
- },
1425
- closeBill(input) {
1426
- return action({ action: "close", ...input }, "Bill could not be closed.");
1427
- },
1428
- postPayment(input) {
1429
- return action(
1430
- { action: "post_payment", ...input, checkNumber: input.checkNumber ?? "" },
1431
- "Payment could not be posted."
1432
- );
1433
- },
1434
- submitSecondReview(input) {
1435
- return action(
1436
- { action: "second_review", ...input },
1437
- "Second Review could not be submitted."
1438
- );
1439
- },
1440
- async startCorrection() {
1441
- const response = await mutation(
1442
- "/partner/v2/browser/actions",
1443
- {
1444
- method: "POST",
1445
- headers: { "content-type": "application/json" },
1446
- body: JSON.stringify({ action: "start_correction" })
1447
- },
1448
- "Correction draft could not be created."
1449
- );
1450
- const body = await response.json();
1451
- if (typeof body.replacementBillId !== "string") {
1452
- throw new Error("The billing service did not return the correction bill ID.");
1453
- }
1454
- return {
1455
- replacementBillId: body.replacementBillId,
1456
- data: normalizeLifecycle(body.data)
1457
- };
1458
- }
1459
- };
1460
- }
1461
1200
  function openPdf(blob, filename) {
1462
1201
  const url = URL.createObjectURL(blob);
1463
1202
  const link = document.createElement("a");
@@ -2003,8 +1742,9 @@ export {
2003
1742
  MindBillBillReview,
2004
1743
  MindBillBillTimeline,
2005
1744
  buildBillReviewSaveInput,
2006
- createBillLifecycleClient,
1745
+ createBillLifecycleClient2 as createBillLifecycleClient,
2007
1746
  createBillStatusClient,
1747
+ ensureTrailingProcedureLine,
2008
1748
  mindBillAppearanceStyle,
2009
1749
  mindBillThemePresets,
2010
1750
  resolveMindBillAppearance,