Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Header from '../src/components/header';
import Patient from '../src/components/patient';
import PatientData from './components/patientData';
import Records from './components/records';
import InsuranceCard from './components/insuranceCard';
import { BrowserRouter as Router} from "react-router-dom";
import { TabPanel, Tabs } from '@cmsgov/design-system';

Expand All @@ -24,6 +25,7 @@ function App() {
</div>
{}
<Records />
<InsuranceCard />
{}
<div>
<div>
Expand Down
111 changes: 111 additions & 0 deletions client/src/components/insuranceCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Table, TableCaption, TableRow, TableCell, TableHead, TableBody } from '@cmsgov/design-system';
import React, { useEffect, useState } from 'react';
import * as process from 'process';

export type InsuranceCardField = {
label: string,
value: string
}

export type ErrorResponse = {
type: string,
content: string,
}

export default function InsuranceCard() {
const [fields, setFields] = useState<InsuranceCardField[]>([]);
const [message, setMessage] = useState<ErrorResponse>();

/*
* DEVELOPER NOTES:
* The $generate-digital-insurance-card operation returns a FHIR Bundle containing
* CARIN Digital Insurance Card (C4DIC) resources (Patient, Coverage, Organization).
* Here we pull out a few common fields for display purposes. You will want to
* inspect the actual Bundle returned by your environment and adjust the parsing
* to fit the fields your application needs.
*/
useEffect(() => {
const test_url = process.env.TEST_APP_API_URL ? process.env.TEST_APP_API_URL : '';
fetch(`${test_url}/api/data/insurancecard`)
.then(res => {
return res.json();
}).then(insuranceCardData => {
if (insuranceCardData.entry) {
const coverageEntry = insuranceCardData.entry.find(
(e: any) => e.resource?.resourceType === 'Coverage'
);
const organizationEntry = insuranceCardData.entry.find(
(e: any) => e.resource?.resourceType === 'Organization'
);
const coverage = coverageEntry?.resource;
const organization = organizationEntry?.resource;

const cardFields: InsuranceCardField[] = [
{ label: 'Payer', value: organization?.name || 'Unknown' },
{ label: 'Member ID', value: coverage?.subscriberId || 'Unknown' },
{ label: 'Plan', value: coverage?.class?.[0]?.name || 'Unknown' },
{ label: 'Group Number', value: coverage?.class?.[0]?.value || 'Unknown' },
];
setFields(cardFields);
} else {
if (insuranceCardData.message) {
setMessage({ "type": "error", "content": insuranceCardData.message || "Unknown" })
}
}
});
}, [])

if (message) {
return (
<div className='full-width-card'>
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
<TableCaption>Error Response</TableCaption>
<TableHead>
<TableRow>
<TableCell id="ic_column_1">Type</TableCell>
<TableCell id="ic_column_2">Content</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell stackedTitle="Type" headers="ic_column_1">
{message.type}
</TableCell>
<TableCell stackedTitle="Content" headers="ic_column_2">
{message.content}
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
);
} else {
return (
<div className='full-width-card'>
<Table className="ds-u-margin-top--2" stackable stackableBreakpoint="md">
<TableCaption>Digital Insurance Card</TableCaption>
<TableHead>
<TableRow>
<TableCell id="ic_column_1">Field</TableCell>
<TableCell id="ic_column_2">Value</TableCell>
</TableRow>
</TableHead>
<TableBody>
{fields.map(field => {
return (
<TableRow key={field.label}>
<TableCell stackedTitle="Field" headers="ic_column_1">
{field.label}
</TableCell>
<TableCell stackedTitle="Value" headers="ic_column_2">
{field.value}
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
);
}
}
45 changes: 44 additions & 1 deletion server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
interface User {
authToken?: AuthorizationToken,
eobData?: any,
insuranceCardData?: any,
errors?: string[]
}

const BENE_DENIED_ACCESS = "access_denied"
const FE_MSG_ACCESS_DENIED = "Beneficiary denied app access to their data"
const ERR_QUERY_EOB = "Error when querying the patient's EOB!"
const ERR_QUERY_INSURANCE_CARD = "Error when querying the patient's digital insurance card!"
const ERR_MISSING_AUTH_CODE = "Response was missing access code!"
const ERR_MISSING_STATE = "State is required when using PKCE"

Expand All @@ -31,6 +33,7 @@
function clearBB2Data() {
loggedInUser.authToken = undefined;
loggedInUser.eobData = {};
loggedInUser.insuranceCardData = {};
}

// AuthorizationToken holds access grant info:
Expand All @@ -47,7 +50,7 @@
// where <v2 scopes> is space delimited v2 scope specs (url encoded)
// e.g. patient/ExplanationOfBenefit.rs
const redirectUrl = bb.generateAuthorizeUrl(authData) +
"&scope=patient%2FExplanationOfBenefit.rs"
"&scope=patient%2FExplanationOfBenefit.rs%20patient%2FPatient.rs%20patient%2FCoverage.rs"
res.send(redirectUrl);
});

Expand Down Expand Up @@ -90,10 +93,43 @@
process.stdout.write(ERR_QUERY_EOB + '\n');
process.stderr.write("Exception: " + String(e) + '\n');
if (e.response) {
console.log("Error status:", e.response.status);

Check warning on line 96 in server/index.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
console.log("Error data:", e.response.data);

Check warning on line 97 in server/index.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
}
}

try {
// data flow: call the $generate-digital-insurance-card operation to get the
// beneficiary's CARIN Digital Insurance Card (C4DIC) FHIR bundle.
// This operation is only available on BB2 v3.
const insuranceCardResults = await bb.getInsuranceCardData(authToken);
authToken = insuranceCardResults.token; // in case authToken got refreshed

loggedInUser.authToken = authToken;

// the SDK does not throw on HTTP error responses (e.g. missing scope, 404),
// it resolves with the error body in response.data, so check status here
const status = insuranceCardResults.response?.status;
if (status && status >= 200 && status < 300) {
loggedInUser.insuranceCardData = insuranceCardResults.response?.data;
} else {
process.stdout.write(ERR_QUERY_INSURANCE_CARD + '\n');
process.stdout.write("Insurance card response status: " + String(status) + '\n');
process.stdout.write(
"Insurance card response data: " +
JSON.stringify(insuranceCardResults.response?.data) + '\n'
);
loggedInUser.insuranceCardData = {"message": ERR_QUERY_INSURANCE_CARD};
}
} catch (e: any) {
loggedInUser.insuranceCardData = {"message": ERR_QUERY_INSURANCE_CARD};
process.stdout.write(ERR_QUERY_INSURANCE_CARD + '\n');
process.stderr.write("Exception: " + String(e) + '\n');
if (e.response) {
console.log("Error status:", e.response.status);

Check warning on line 129 in server/index.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
console.log("Error data:", e.response.data);

Check warning on line 130 in server/index.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
}
}
} else {
clearBB2Data();
process.stdout.write(ERR_MISSING_AUTH_CODE + '\n');
Expand Down Expand Up @@ -135,6 +171,13 @@
}
});

// data flow: front end fetch the digital insurance card ($generate-digital-insurance-card)
app.get("/api/data/insurancecard", (req: Request, res: Response) => {
if (loggedInUser.insuranceCardData) {
res.json(loggedInUser.insuranceCardData);
}
});

const port = 3001;
app.listen(port, () => {
process.stdout.write(`[server]: Server is running at https://localhost:${port}`);
Expand Down
Loading