Components
React
Add a complete connected billing workspace or compose submission, search, reporting, and lifecycle surfaces individually.
Add billing to your application
Install the package; components include their own styles. First connect an authenticated server session route; the examples below assume that route is ready.
pnpm add @mindbill/react@0.73.0- Billing page:
ConnectedBillingWorkspaceprovides task queues, All Bills, reports, bill details, and a Settings tab. - Case billing tab:
BillSubmissionFormbefore submission;ConnectedBillLifecycleafterward. - Billing settings: use the built-in Settings tab, or mount
BillingSettingsseparately. The server requires an admin-authorized session.
Example: place components in your existing routes
"use client";
import {
ConnectedBillingWorkspace, ConnectedBillLifecycle,
BillSubmissionForm, BillingSettings, type BillSubmissionFormProps,
} from "@mindbill/react";
// Sidebar -> /billing. The workspace owns bill selection and detail navigation.
export function BillingPage({ canManageBilling }: { canManageBilling: boolean }) {
return <ConnectedBillingWorkspace
sessionEndpoint="/api/mindbill/session"
showSettings={canManageBilling}
billingSettings={{ sessionEndpoint: "/api/mindbill/settings-session" }}
style={{ height: "calc(100dvh - 96px)", minHeight: 0 }}
onCreateBill={() => { window.location.href = "/billing/new"; }}
/>;
}
// Case -> Billing tab after Report. Host server authorizes the case and resolves
// its saved bill ID; return a bill-scoped session from this separate host route.
export function CaseBilling({ caseId, billId }: { caseId: string; billId: string }) {
return <ConnectedBillLifecycle billId={billId}
sessionEndpoint={"/api/cases/" + encodeURIComponent(caseId) + "/billing-session"} />;
}
// Map existing structured case data to BillSubmissionInput, persist result.billId
// using your existing case metadata/API, and keep unknown fields editable.
export function NewBill({ canManageBilling, ...billProps }: Pick<
BillSubmissionFormProps, "initialBill" | "attachments" | "onSubmitted"
> & { canManageBilling: boolean }) {
return <BillSubmissionForm {...billProps}
{...(canManageBilling ? { billingSettings: {
sessionEndpoint: "/api/mindbill/settings-session"
} } : {})}
sessionEndpoint="/api/mindbill/session" />;
}
// Optional standalone settings page; the workspace already has a Settings tab.
// This administrator-only endpoint grants organization:manage.
export function PracticeBillingSettings() {
return <BillingSettings sessionEndpoint="/api/mindbill/settings-session" />;
}Keep the returned billId in your existing case metadata. Send your case ID as externalId for correlation; it is not a uniqueness guarantee. Never create a bill on component mount.
Give the workspace a bounded height and min-height: 0 in flex ancestors when your app disables page scrolling.
Component catalog
All React exports and when to use them
FeeScheduleCalculatorCalculate multiple California treatment lines with modifiers, sources, and coding review. Integration guide.Through your reference clientBillSubmissionFormYou want the complete form, reference data, validation, attachments, and atomic Submit action.YesReportAutofillReview optional, capability-gated PDF suggestions before applying them to empty fields.Yes, or through your adapterBillSubmission*SectionYou want the same component-owned form state with individually composable sections.Yes, through the parentConnectedBillingWorkspaceYou want Bill Tasks, All Bills, reports, settings, and per-bill lifecycle in one integrated surface.YesConnectedBillSearchYou need the authoritative bill registry with patient, bill, claim, status, and A/R filters.YesConnectedBillTasksDashboardYou need actionable billing queues grouped by task and age.YesConnectedServiceLineItemsReportYou need original submissions grouped by procedure code.YesConnectedProductivityReportYou need created, transmitted, submitted, and acceptance performance by biller.YesBillingDashboardYou need receivables KPIs, aging buckets, search, filters, and a responsive bill list, plus optional settings.Settings onlyBillListYou need only the searchable and filterable bill directory.NoBillAgingSummaryYou need only receivables and aging KPIs.NoBillingReportYou need grouped status, payer, or aging reporting.NoBillStatusAgingMatrixYou need the status × aging management grid with drill-down cells and totals.NoBillReadOnlyFormYou want the same bill layout after submission without editing.NoConnectedBillLifecycleYou want the complete post-submission workflow.YesuseBillLifecycleYou want custom post-submission lifecycle UI.YesConnectedBillStatusYou need compact status and valid next actions.YesuseBillStatusYou want custom status UI.YesBillStatusSummaryYou need a presentational status card.NoBillLifecycleActionsYou need state-aware actions in your own layout.NoBillLifecycleProgressYou need the horizontal bill lifecycle.NoBillSnapshotSummaryYou need a compact submitted CMS-1500 snapshot.NoBillExplanationOfReviewYou need the consolidated EOR, denial, remittance, and payment reconciliation surface.NoBillRemittanceCardYou need payer-reported, posted, and balance amounts.NoBillPayerContactCardYou need payer and adjuster follow-up contacts.NoBillPaymentLedgerYou need posted payment history.NoBillActivityTimelineYou want to render the bill's complete history.NoMindBillBillReviewYou prefer the hosted post-submission review surface.HostedMindBillBillTimelineYou prefer the hosted timeline surface.HostedComplete submission form
BillSubmissionForm owns which fields exist and which are required. It renders red asterisks, validates the values, resolves billing reference data through a short-lived browser session, lets users review prefilled documents and add uploads, and renders the Submit button. It never creates a MindBill draft.
Submission session permissions
This excerpt assumes your server has authenticated the user, checked membership, and selected the organization credential. See the complete server route for origin checks and error handling.
app.post("/api/mindbill/submission-session", async (req, res) => {
const user = await requireSignedInUser(req);
res.json(await mindbill.createBrowserSession({
subject: user.id,
permissions: ["bills:create", "payers:read"],
allowedOrigin: process.env.APP_ORIGIN!,
expiresIn: 900,
}));
});import { BillSubmissionForm } from "@mindbill/react";
<BillSubmissionForm
initialBill={toBillSnapshot(caseRecord)}
attachments={selectedBillingDocuments}
sessionEndpoint="/api/mindbill/submission-session"
appearance={{ preset: "orange-bright" }}
submitLabel="Submit bill"
onSubmitted={({ billId }) => rememberBillId(billId)}
/>Optionally pass reportAutofill with a separately authorized connection, or use ReportAutofill in a custom form. The report autofill guide covers access, explicit review, and preserving existing values.
Prefill case data and final-report attachments
Map data you already have: patient name, date of birth and address; claim, employer and injury date; service date; diagnosis codes; procedure codes, modifiers and units. Keep missing values editable. Confirm a canonical claims administrator in the payer picker; a text label from a report is not a verified payer ID.
Use your existing structured extraction results, or opt into the capability-gated report autofill component from React 0.67.0. Review suggestions before applying them to empty fields. Do not infer clinical codes or fabricate missing information.
// Host adapter example: rename these source fields to your existing structured data.
// This med-legal adapter omits treatment charges and line-level diagnosis pointers.
// For professional treatment, use /learn/treatment-quickstart.
// Do not infer clinical codes or upload every case document during SDK setup.
export function billFromCase(caseRecord) {
return {
externalId: String(caseRecord.id),
patient: {
firstName: caseRecord.patient.firstName ?? "",
lastName: caseRecord.patient.lastName ?? "",
dateOfBirth: caseRecord.patient.dateOfBirth ?? "", // YYYY-MM-DD
address: {
line1: caseRecord.patient.address?.line1 ?? "",
city: caseRecord.patient.address?.city ?? "",
state: caseRecord.patient.address?.state ?? "",
postalCode: caseRecord.patient.address?.postalCode ?? "",
},
},
claim: {
claimNumber: caseRecord.claimNumber ?? "",
employer: caseRecord.employerName,
dateOfInjury: caseRecord.dateOfInjury,
},
service: { date: caseRecord.dateOfService ?? "" },
diagnoses: caseRecord.diagnosisCodes ?? [],
serviceLines: (caseRecord.billingLines ?? []).map((line) => ({
code: line.code, modifiers: line.modifiers, units: line.units,
})),
// Optional: billingProvider, renderingProvider, serviceLocation from
// your saved host settings. Otherwise let the user supply/review them.
};
}
// Only pass the finalized report. An empty list leaves upload to the user.
// documentId identifies a HOST document, not a MindBill document.
export function finalReportSources(documentId) {
if (!documentId) return [];
return [{
id: documentId, fileName: "final-report.pdf",
documentType: "final_report", autoAttached: true, removable: true,
loadBlob: async () => {
// Existing authenticated host endpoint: enforce document/case ownership.
const response = await fetch("/api/documents/" + encodeURIComponent(documentId), {
credentials: "same-origin", cache: "no-store",
});
if (!response.ok) throw new Error("Unable to load finalized report");
return response.blob();
},
}];
}Pass billFromCase(caseRecord) as initialBill and finalReportSources(finalizedReportId) as attachments. All supplied source attachments are included unless removed: do not pass every case document or rely on selected: false. Users can add proof of service and other supporting PDFs in the form, including a final report edited outside your app.
Responsive two-column fields, paste-friendly MM/DD/YYYY dates, required asterisks, and authenticated ZIP-to-city/state completion.
Complete server-backed ICD-10 search with common-injury quick picks and removable chips, plus canonical claims-administrator search backed by MindBill payer routing IDs.
Searchable workers'-comp procedure and modifier controls, evaluation-mode modifier defaults, medical-legal fee-schedule amounts, totals, valid manual CPT/HCPCS entry, and one automatically maintained empty line.
Removable source documents, a locked auto-attached practice W-9, and click, panel-drop, or whole-page PDF upload.
Individual form sections
Use the named section exports when your product needs to place form sections in its own page shell. BillSubmissionForm remains the single state, validation, lookup, upload, and submission engine; its children only control composition and order.
import {
BillSubmissionActions,
BillSubmissionAttachmentsSection,
BillSubmissionClaimSection,
BillSubmissionForm,
BillSubmissionHeader,
BillSubmissionPatientSection,
BillSubmissionProvidersSection,
BillSubmissionServiceLinesSection,
} from "@mindbill/react";
<BillSubmissionForm
initialBill={bill}
attachments={documents}
sessionEndpoint="/api/mindbill/submission-session"
onSubmitted={({ billId }) => rememberBillId(billId)}
>
<BillSubmissionHeader />
<BillSubmissionPatientSection />
<BillSubmissionClaimSection />
<BillSubmissionProvidersSection />
<BillSubmissionServiceLinesSection />
<BillSubmissionAttachmentsSection />
<BillSubmissionActions />
</BillSubmissionForm>Dashboard, aging, bill list, and reporting
ConnectedBillingWorkspace is the default partner integration. It owns fetching, filters, drill-down navigation, selected views, loading and error states, and per-bill lifecycle actions. Use an organization-wide session with bills:create, bills:read, bills:act, documents:read, payers:read, and eors:read. A create-only submission session cannot load the workspace.
import { ConnectedBillingWorkspace } from "@mindbill/react";
<ConnectedBillingWorkspace
sessionEndpoint="/api/mindbill/session"
appearance={{ preset: "calm-clinical" }}
onCreateBill={() => navigate("/billing/new")}
style={{ height: "calc(100dvh - 96px)", minHeight: 0 }}
/>From React 0.64.0, both ConnectedBillingWorkspace and BillingDashboard include a Settings tab by default. Set showSettings={false} to hide it. Set billingSettings={{ sessionEndpoint: "/api/mindbill/settings-session" }} for a dedicated administrator session; otherwise the workspace reuses its main connection and BillingDashboard uses /api/mindbill/session. The server requires organization:manage and the component does not expand permissions. Use onSettingsSaved(profile) to refresh host state after a save; the callback receives OrganizationProfileData. The workspace also accepts initialView="settings". See the settings integration example.
React 0.50.0 includes a Payment review tab for confirmed cash, with received-date filters, search, totals, bill drill-down, and page export. Set initialView="payments" or use ConnectedPaymentReview independently. See payment-review data and access rules. The workspace manages its own bill-detail navigation.
Use ConnectedBillSearch independently when your product already has its own navigation. It searches bill identifiers, patient and administrator names, statuses, procedure codes, and dates, and combines that search with status, billing-provider, claims-administrator, A/R-age, and date filters.
import { ConnectedBillSearch } from "@mindbill/react";
<ConnectedBillSearch
sessionEndpoint="/api/mindbill/session"
initialQuery={{
q: "Example 99213",
dateField: "service",
from: "2026-09-01",
to: "2026-09-30",
status: "submitted",
}}
onSelectBill={(bill) => openBill(bill.id)}
/>The connected dashboard and reports use the same thin-client session contract. Counts and drill-downs come from MindBill's authoritative server-side queries, while CSV and PDF controls remain in the component surface.
import {
ConnectedBillTasksDashboard,
ConnectedProductivityReport,
ConnectedServiceLineItemsReport,
} from "@mindbill/react";
<ConnectedBillTasksDashboard sessionEndpoint="/api/mindbill/session" />
<ConnectedServiceLineItemsReport sessionEndpoint="/api/mindbill/session" />
<ConnectedProductivityReport sessionEndpoint="/api/mindbill/session" />The operations exports accept the same normalized bill summaries. They calculate receivables and aging in the browser, render responsive tables or mobile cards, and keep navigation under your application's control.
import { BillingDashboard } from "@mindbill/react";
<BillingDashboard
bills={bills}
billingSettings={{ sessionEndpoint: "/api/mindbill/settings-session" }}
heading="Billing operations"
description="Search every bill and act on aging balances."
onSelectBill={(bill) => navigate(`/billing/${bill.id}`)}
appearance={{ preset: "orange-bright" }}
/>BillingReport groups the same data by payer, lifecycle status, or aging bucket. buildBillingReportCsv returns a ready-to-download or copyable CSV without requiring a second reporting schema.
import {
BillingReport,
buildBillingReportCsv,
} from "@mindbill/react";
<BillingReport bills={bills} groupBy="payer" />;
const csv = buildBillingReportCsv(bills, "payer");BillStatusAgingMatrix renders the management view billing teams expect from legacy tools: one row per lifecycle status, one column per 0–30 / 31–60 / 61–90 / 91+ aging bucket, clickable counts with outstanding balances, and row, column, and grand totals. Every onSelectCell payload carries { state, bucket, count, balance, bills } — the exact bills behind the count — so a drill-down never needs a second query. Pin your lifecycle ordering with stateOrder; buildBillStatusAgingMatrix and buildBillStatusAgingCsv expose the same aggregation presentation-free.
import { BillStatusAgingMatrix } from "@mindbill/react";
<BillStatusAgingMatrix
bills={bills}
appearance={{ preset: "clinical-blue" }}
onSelectCell={(cell) => setDrillDown(cell)}
/>
{drillDown ? <BillList bills={drillDown.bills} onSelectBill={openBill} /> : null}Search and date filters
React 0.67.0 adds patient, rendering-provider, and claims-administrator selectors backed by the complete authorized filter inventory, independent of the current page. Use canonical IDs: patientId, renderingProviderId, and claimsAdministrator in browser registry queries; the last serializes to claimsAdminId on the API. Filters never expand record access.
The connected bill registry searches patient and claims-administrator names, bill and claim identifiers, external IDs, statuses, procedure codes, and dates. Every search word must match; matching ignores case. Enter text and an optional service-date or submission-date range, then press Search or Enter. Status, age, patient, rendering-provider, and claims-administrator filters apply immediately. Clear resets all filters.
For host-owned BillingDashboard rows, filtering happens immediately in the supplied array. Include dateOfService and procedureCodes for those searches. Connected queries use q, dateField (service or submitted), and inclusive from/to dates. See the dashboard query reference.
Organization onboarding
OrganizationOnboarding starts with the pay-to billing provider, then rendering providers, locations, and W-9. Organization details appear in a separate disclosure and remain required when requested by the onboarding checklist. The wizard saves through an organization-wide organization:manage session, and onCompleted fires when billing setup is complete.
React 0.65.0 adds three sections to BillingSettings: Billing profiles, Claims administrators, and Team. Each administrative list loads when its section is selected. Team requires the separate team:manage permission and manages existing MindBill login accounts only; it does not invite users or change host-app roles. Physician signatures are configured in MindBill rendering-provider settings. See the settings permission guide.
From React 0.47.0, settings accept EIN or SSN with an explicit tax ID type. Saved SSNs are encrypted and masked in responses. A blank saved SSN field preserves it, a replacement changes it, and the clear button requests removal on save. Use organizationProfileOptions(profile) for SSN-backed saved billing choices so submission sends a provider reference rather than a masked identifier. See the saved-profile details below.
import { OrganizationOnboarding } from "@mindbill/react";
<OrganizationOnboarding
sessionEndpoint="/api/mindbill/settings-session"
appearance={{ preset: "clinical-blue" }}
onCompleted={() => enableBillingFeatures()}
/>If your app already stores and extracts W-9s, React 0.50.0's W9Upload renders the upload, current document, extraction progress, retry, and review states using your host callbacks. It does not create another document store or parser. See choose one W-9 storage owner.
Saved profiles, provider references, and tax ID handling
If your app already stores billing provider, rendering provider, service locations, and W-9 documents, keep that ownership and prefill from it. Otherwise offer MindBill's settings components to avoid building and maintaining duplicate input screens. See the complete billing-settings guide for setup and permissions.
Use a separate admin-authorized session endpoint for organization:manage. Do not grant it to every billing user. Avoid persisting duplicate tax identifiers; keep sensitive values out of browser storage, logs, analytics, screenshots, and coding-agent prompts.
For saved choices during bill creation, GET /partner/v2/organization/billing-profile accepts an organization-wide browser session with bills:create and returns a masked organization profile. Bill-scoped sessions cannot perform this lookup. Settings writes still require organization:manage; reading choices does not grant permission to edit them.
From React 0.62.0, connected forms automatically load saved choices when profileOptions is omitted. Pass a separate billingSettings session configuration to enable the prebuilt Add/edit settings flow for administrators. Explicit profileOptions override the lookup; an empty object suppresses it.
// Requires @mindbill/react >=0.62.0.
"use client";
import { BillSubmissionForm, type BillSubmissionFormProps,
type BillSubmissionInput } from "@mindbill/react";
export function ProfileBasedBill({ initialBill, canManageBilling }: {
initialBill: BillSubmissionInput; canManageBilling: boolean;
}) {
return <BillSubmissionForm initialBill={initialBill}
sessionEndpoint="/api/mindbill/session"
profileDisplay="compact"
{...(canManageBilling ? { billingSettings: {
sessionEndpoint: "/api/mindbill/settings-session"
} } : {})} />;
}
// The connected form reads saved choices automatically when profileOptions is omitted.
// The billing endpoint needs organization-wide bills:create, not organization:manage.
// billingSettings enables the prebuilt Add/edit settings flow. Its SEPARATE endpoint
// must enforce the administrator role server-side and grant organization:manage.
// Hiding a button is not an authorization check. Omit billingSettings for other users.
// Existing case values remain unchanged until the user explicitly selects a profile.
// Host-managed alternative: explicit choices override automatic organization lookup.
export function HostProfileBill({ initialBill, savedBillingProvider,
savedRenderingProvider, savedServiceLocation }: {
initialBill: BillSubmissionFormProps["initialBill"];
savedBillingProvider: NonNullable<BillSubmissionInput["billingProvider"]>;
savedRenderingProvider: NonNullable<BillSubmissionInput["renderingProvider"]>;
savedServiceLocation: NonNullable<BillSubmissionInput["serviceLocation"]>;
}) {
const profileOptions = {
billingProviders: [{ id: "practice-1", label: "Main practice", value: savedBillingProvider }],
renderingProviders: [{ id: "doctor-1", label: "Rendering doctor", value: savedRenderingProvider }],
serviceLocations: [{ id: "office-1", label: "Main office", value: savedServiceLocation }],
};
return <BillSubmissionForm initialBill={initialBill} profileOptions={profileOptions}
profileDisplay="compact" sessionEndpoint="/api/mindbill/session" />;
}
// profileOptions={} also suppresses the organization-wide lookup when it is unwanted.
// Bill-scoped sessions cannot read organization-wide choices. Do not widen access.
// Omit profileDisplay (or use "expanded") to show all fields.
// Selecting a profile does not mutate the saved profile.
// SSN-backed choices retain savedProviderId; the server resolves the identifier.
// Never put taxIdLast4 in taxId, browser storage, telemetry or agent prompts.
// Settings supports taxIdType: "EIN" | "SSN" (default EIN).
// Saved SSNs are encrypted. Responses contain empty taxId, taxIdLast4 and
// taxIdConfigured. In settings: blank preserves, replacement changes, clear removes.
// Custom settings API writes: omit taxId to preserve; taxId: "" explicitly clears.
// Do not send response-only taxIdLast4/taxIdConfigured in a settings write.profileDisplay="compact" presents saved choices first; "expanded" keeps all fields visible. Host-managed options need no extra storage or organization lookup. Settings support taxIdType: "EIN" | "SSN"; EIN remains the default. Select SSN explicitly rather than putting it in an EIN field.
Saved SSNs are encrypted and returned as an empty taxId with taxIdLast4 and taxIdConfigured, not as plaintext. In settings, leaving a saved SSN input blank preserves it; entering a replacement changes it; the explicit clear action removes it on save. The components handle these write semantics. Custom API forms should omit taxId to preserve it and send taxId: "" to clear it; do not send read-only masking metadata back in a settings write.
organizationProfileOptions(profile) creates a server-resolved provider reference for an SSN-backed profile. Do not copy the last four digits into a bill's tax ID. The browser/API create contract accepts billingProvider: { savedProviderId }. Corrections and duplicates can use billingProvider: { sourceBillId } to reuse that bill's immutable provider snapshot. Both references are resolved within the authenticated organization. Existing TypeScript code should narrow reference versus inline-provider values before reading fields such as name or taxId.
Treatment and authorization requests
Use billingMode="professional" in the initial bill for treatment charges and per-line diagnosis selection. RfaDashboard provides authorization tracking, draft creation and revision-aware editing, clinical PDF upload, authorized signing, packet and cover-sheet review, directory destination selection, explicit fax sending, delivery evidence, receipt and information-request recording, item decisions, and a task board for no-response follow-up, matching incoming response faxes, and posting utilization review decisions. Supporting PDFs can be selected in the creation form. Use RfaTaskBoard to embed the work queue separately. Configure its role-matched permissions, human signer identity, and environment; it defaults to read-only sandbox. See the treatment quickstart and RFA integration guide.
Notification settings
Administrative recipient list (React 0.52.0): NotificationRecipientsSettings (alias ConnectedNotificationRecipientsSettings) lets an administrator invite any authorized email address, including someone without a console account. Choose practice-wide or explicitly assigned-bill access, status/payment alerts, aging reminders, quiet hours and daily/weekly billing-activity digests. Everything stays off until the email owner reviews and confirms the invitation; the list includes pending states and a disable action.
"use client";
import { useMemo } from "react";
import {
NotificationRecipientsSettings,
type NotificationRecipientsAdapter,
} from "@mindbill/react";
export function BillingRecipients({ identityKey, csrfToken }: {
identityKey: string; // changes for admin, practice OR sandbox/live environment
csrfToken: string;
}) {
const adapter = useMemo<NotificationRecipientsAdapter>(() => {
async function request(method: string, suffix = "", body?: unknown) {
// YOUR authenticated admin route; not a MindBill browser-session endpoint.
const response = await fetch("/api/billing/notification-recipients" + suffix, {
method, credentials: "same-origin", cache: "no-store",
headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error("Notification request failed");
return response.json(); // host returns API data, not the { data } envelope
}
return {
load: offset => request("GET", "?offset=" + offset),
invite: input => request("POST", "", input), // preserve input.requestId
disable: async externalUserId => {
await request("DELETE", "/" + encodeURIComponent(externalUserId));
},
};
}, [identityKey, csrfToken]);
return <NotificationRecipientsSettings identityKey={identityKey} adapter={adapter}
appearance={{ preset: "mindbill" }} />;
}Connect load, invite and disable to your authenticated host administrator route, return upstream data rather than its envelope, and preserve invitation request IDs on unchanged retries. Forward reportDigest (off, daily or weekly) through your server adapter. Keep API keys and bill-assignment authority on the server. See the invitation API, receipt handling and required host checks. Digests contain activity counts, not financial reports or patient attachments; sandbox sends no email.
Personal preferences
NotificationSettings (alias ConnectedNotificationSettings) adds a default-off email preferences panel to your existing settings page. It supports explicit consent, status updates, 30/60/90-day aging reminders, quiet hours, daily/weekly billing-activity digests, and unsubscribe for any partner's users, including people without a console account. Return reportDigest in your adapter's preference snapshot. New or changed digest schedules require fresh consent.
"use client";
import { useMemo } from "react";
import {
NotificationSettings,
type NotificationSettingsAdapter,
type NotificationSettingsSnapshot,
} from "@mindbill/react";
export function BillingNotificationSettings({ identityKey, csrfToken }: {
identityKey: string; // Change when signed-in user, practice, or environment changes.
csrfToken: string; // Issued by your existing host CSRF protection.
}) {
const adapter = useMemo<NotificationSettingsAdapter>(() => {
const endpoint = "/api/mindbill/notification-settings";
let pending: { payload: string; id: string } | null = null;
async function request(method: string, body?: unknown, requestId?: string) {
const response = await fetch(endpoint, {
method, credentials: "same-origin", cache: "no-store",
headers: {
"Content-Type": "application/json",
...(method === "GET" ? {} : { "X-CSRF-Token": csrfToken }),
...(requestId ? { "X-Notification-Request-Id": requestId } : {}),
},
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) {
if (response.status === 409) pending = null; // Requires a NEW checkbox confirmation.
throw new Error("Could not confirm notification settings");
}
return response;
}
async function load(): Promise<NotificationSettingsSnapshot> {
return (await request("GET")).json();
}
return {
load,
save: async update => {
const payload = JSON.stringify(update);
if (pending?.payload !== payload) pending = { payload, id: crypto.randomUUID() };
await request(update.enabled ? "PUT" : "DELETE",
update.enabled ? update : undefined, pending.id);
const snapshot = await load(); // PUT is not a complete settings response.
pending = null;
return snapshot;
},
unsubscribe: async () => { pending = null; await request("DELETE"); return load(); },
};
}, [identityKey, csrfToken]);
return <NotificationSettings identityKey={identityKey} adapter={adapter}
appearance={{ preset: "calm-clinical" }} />;
}Your adapter calls an authenticated host-server route and reloads GET after each PUT or DELETE. The server owns identity, verified email, practice/assigned-bill access, consent records, and the permanent API key. Change identityKey when the host user, practice, or environment changes. Use appearance, className, and style for theming. See the copyable server adapter and required host checks before wiring this up.
Post-submission setup
Once a bill exists, add one authenticated server route that exchanges your signed-in user for a short-lived, organization-scoped browser session restricted to that submitted bill.
Bill-scoped session permissions
This excerpt assumes existing authentication, bill-access checks, and the correct organization credential. Apply the server route protections before issuing sessions.
app.post("/api/mindbill/bills/:billId/session", async (req, res) => {
const user = await requireSignedInUser(req);
const { billId } = req.params;
await requireBillAccess(user, billId);
res.json(await mindbill.createBrowserSession({
subject: user.id,
permissions: ["bills:read", "bills:act", "documents:read", "eors:read"],
resource: { billId },
allowedOrigin: process.env.APP_ORIGIN!,
expiresIn: 900,
}));
});Complete post-submission lifecycle
Pass the returned billId. ConnectedBillLifecycle never creates or edits a pre-submission draft.
import { ConnectedBillLifecycle } from "@mindbill/react";
export function SubmittedBill({ billId }: { billId: string }) {
return (
<ConnectedBillLifecycle
billId={billId}
sessionEndpoint={`/api/mindbill/bills/${billId}/session`}
appearance={{ preset: "orange-bright" }}
/>
);
}The component includes lifecycle progress, the frozen bill snapshot, a consolidated EOR and payment reconciliation surface, rich claims-administrator directory details, history, packet preview, and a sticky state-aware action bar for Second Review, correction, IBR, lien, payment, or closure when eligible.
Shared bill detail sections and related records
React 0.66.0 exports BillDetailLayout and BillDetailSection, also used by BillReadOnlyForm. A section accepts a title, description, actions, and validationIssues containing severity: "error" | "warning" and a message. Errors appear in red and warnings in amber, with accessible text lists. Supply your authoritative validation results; these visual components do not validate or submit a bill.
import { BillDetailLayout, BillDetailSection } from "@mindbill/react";
export function PatientDetails() {
return <BillDetailLayout header={<h2>Bill details</h2>}>
<BillDetailSection title="Patient" validationIssues={[
{ severity: "error", message: "Enter the patient's date of birth." },
{ severity: "warning", message: "Review the mailing address." },
]}>
<p>Your patient fields or read-only details</p>
</BillDetailSection>
</BillDetailLayout>;
}From React 0.67.0, ConnectedBillLifecycle derives client validation and rejection field issues for editable current bills in incomplete, draft, or rejected states. An explicit validationIssues value overrides that default. Other states do not gain draft validation errors.
The layout accepts optional header, actions, and sidebar slots. Sections accept headerClassName, bodyClassName, className, and style for host styling. On BillReadOnlyForm, group validationIssues by patient, claim, providers, services, or attachments.
Pass onPatientClick(patient), onRenderingProviderClick(provider), and onClaimsAdministratorClick(administrator) to BillReadOnlyForm or ConnectedBillLifecycle to make those names open your own related-record view. IDs are optional on historical snapshots: check for a canonical ID before applying a filter, and do not infer identity from a name. On these standalone detail components, names remain plain text without callbacks. In React 0.67.0, ConnectedBillingWorkspace supplies default callbacks that open All Bills filtered by the canonical entity ID and reset prior filters and pagination. Override the corresponding workspace callback to use your host navigation. Historical entities without IDs remain plain text; names are never used as guessed IDs. These callbacks do not change the bill's saved data or grant access to another record.
From React 0.48.0, selecting an Original Bill or another submission opens that submission's Bill details, not the history tab. Historical details are read-only: current-bill actions and balances are not presented as historical facts. Older submissions without a stored snapshot show an explicit availability warning instead of substituting the current bill.
The current bill also includes built-in team notes with author and time across submission attempts and Forward copy. Forwarding previews one combined PDF, then requires confirmation before emailing it; it does not resubmit the bill or change its status. Sandbox forwarding is simulated and sends no email. See communications and permissions.
Pass your own case contacts as courtesyCopyRecipientOptions to offer named To/CC choices alongside manual email entry. For a workspace, use getCourtesyCopyRecipientOptions(billId) to keep suggestions specific to the selected bill. The standalone BillCourtesyCopyForm accepts recipientOptions; each CourtesyCopyRecipientOption has an email and optional name.
// Suggested contacts from your own case record, not automatic recipients.
<ConnectedBillLifecycle
billId={billId}
sessionEndpoint={`/api/mindbill/bills/${billId}/session`}
courtesyCopyRecipientOptions={caseContacts.map((contact) => ({
email: contact.email,
name: contact.name,
}))}
/>
// A workspace can resolve a different contact list for each bill.
<ConnectedBillingWorkspace
sessionEndpoint="/api/mindbill/session"
getCourtesyCopyRecipientOptions={(billId) => contactsByBillId[billId] ?? []}
/>The Second Review dialog supports per-line corrections to units, modifiers, and the explicitly reviewed charge. It does not guess a new charge when units or modifiers change. These corrections apply to the new review submission, preserving the original bill's history.
Custom lifecycle UI
useBillLifecycle exposes the same authoritative submitted state and post-submission actions.
import { useBillLifecycle } from "@mindbill/react";
function BillingToolbar({ billId }: { billId: string }) {
const bill = useBillLifecycle({
billId,
sessionEndpoint: `/api/mindbill/bills/${billId}/session`,
});
if (bill.isLoading) return <p>Loading…</p>;
if (bill.error) return <button onClick={bill.refresh}>Try again</button>;
return (
<>
<strong>{bill.data?.lifecycle.state}</strong>
<button onClick={() => bill.closeBill({ reason: "Completed" })}>
Close bill
</button>
</>
);
}Status surfaces
Open the full lifecycle demo to inspect every state or walk one synthetic bill from Sent through payment and closure.
ConnectedBillStatus loads and refreshes authoritative status. useBillStatus exposes the same client state for a custom layout.
import { ConnectedBillStatus } from "@mindbill/react";
<ConnectedBillStatus
billId={billId}
sessionEndpoint={`/api/mindbill/bills/${billId}/session`}
refreshInterval={30_000}
/>BillStatusSummary is the data-only version when your app already has the status.
import { BillStatusSummary } from "@mindbill/react";
<BillStatusSummary
status="processed"
submittedAt="2026-08-12T17:00:00Z"
agingDays={13}
totalCharge={2015}
totalPaid={0}
balanceDue={2015}
actions={[{ id: "eor", label: "View EOR", onClick: openEor }]}
/>Lifecycle surfaces
These presentational exports use fields already returned by the lifecycle endpoint. Compose them only when the complete connected workspace is more UI than you need.
import { BillLifecycleProgress } from "@mindbill/react";
<BillLifecycleProgress
state={bill.lifecycle.state}
nativeStatus={bill.lifecycle.nativeStatus}
submittedAt={bill.lifecycle.submittedAt}
agingDays={bill.lifecycle.agingDays}
/>import { BillSnapshotSummary } from "@mindbill/react";
<BillSnapshotSummary
bill={bill.bill}
patient={bill.patient}
injury={bill.injury}
delivery={bill.delivery}
/>import {
BillExplanationOfReview,
BillRemittanceCard,
BillPayerContactCard,
BillPaymentLedger,
} from "@mindbill/react";
<BillExplanationOfReview
remittance={bill.remittance}
eors={bill.eors}
payments={bill.payments}
submittedAt={bill.lifecycle.submittedAt}
onOpenEor={previewEor}
/>
// Lower-level legacy surfaces remain available for custom layouts.
<BillRemittanceCard remittance={bill.remittance} />
<BillPayerContactCard delivery={bill.delivery} />
<BillPaymentLedger payments={bill.payments} />Actions and history
BillLifecycleActions renders the action list returned with lifecycle data. Keep the server response authoritative.
import { BillLifecycleActions } from "@mindbill/react";
<BillLifecycleActions
actions={bill.lifecycle.actions}
onAction={(action) => openAction(action.id)}
showUnavailable
/>BillActivityTimeline renders bill.activity. Webhooks remain the durable server-side signal for your own database and analytics.
import { BillActivityTimeline } from "@mindbill/react";
<BillActivityTimeline
events={bill.activity}
appearance={{ preset: "orange-bright" }}
/>Browser clients
Use the framework-neutral clients for submitted bill reads and lifecycle actions outside React rendering or inside your own state layer.
import {
createBillLifecycleClient,
createBillStatusClient,
} from "@mindbill/react";
const lifecycle = createBillLifecycleClient({
billId,
sessionEndpoint: `/api/mindbill/bills/${billId}/session`,
});
const bill = await lifecycle.getLifecycle();
const status = await createBillStatusClient({
billId,
sessionEndpoint: `/api/mindbill/bills/${billId}/session`,
}).getStatus();Hosted wrappers
MindBillBillReview and MindBillBillTimeline wrap hosted post-submission surfaces when native composition is not practical.
import { MindBillBillReview, MindBillBillTimeline } from "@mindbill/react";
<MindBillBillReview sessionToken={token} embedUrl={reviewUrl} />
<MindBillBillTimeline sessionToken={token} embedUrl={timelineUrl} />Appearance and utilities
BILL_SUBMISSION_REQUIRED_FIELDSThe canonical required fields used by BillSubmissionForm.
validateBillSubmissionRun the same submission validation outside the rendered form.
mindBillThemePresetsmindbill, orange-bright, clinical-blue, and the generic warm calm-clinical preset.
resolveMindBillAppearanceResolve a preset plus token overrides.
mindBillAppearanceStyleConvert appearance tokens to CSS custom properties.
ensureTrailingProcedureLineKeep exactly one empty procedure row after populated rows.
summarizeBillingDashboardCalculate outstanding balance, paid totals, status counts, and aging buckets.
buildBillingReportRowsGroup normalized bill summaries by payer, lifecycle status, or aging bucket.
buildBillingReportCsvExport the grouped report as CSV.
buildBillStatusAgingMatrixAggregate bills into the status × aging grid with per-cell bills and totals.
buildBillStatusAgingCsvExport the status × aging grid as CSV.