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
46 changes: 46 additions & 0 deletions apps/intake/src/components/TermsAndConditions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getLegalCompositionForLocation } from 'utils';
import { DisplayTextDef } from 'utils/lib/configuration/types';

interface TermsAndConditionsProps {
pageId: string;
}

export const TermsAndConditions: React.FC<TermsAndConditionsProps> = ({ pageId }) => {
const theme = useTheme();

const legalComposition = getLegalCompositionForLocation(pageId);
if (!legalComposition) {
return null;
}

return (
<Typography color={theme.palette.text.secondary} variant="body2">
{/* this rendering of link and text nodes is pretty generic and could be extracted to its own component at some point */}
{legalComposition.map((node, index) => {
if (node.nodeType === 'Link') {
return (
<Link key={`${node.testId || node.url}-${index}`} to={node.url} target="_blank" data-testid={node.testId}>
<TextNode {...node.textToDisplay} />
</Link>
);
} else {
return <TextNode key={`${node.keyPath || node.literal}-${index}`} {...node} />;
}
})}
{'.'}
</Typography>
);
};

const TextNode = (node: DisplayTextDef): React.ReactElement => {
const { t } = useTranslation();
if (node.keyPath) {
return <>{t(node.keyPath)}</>;
}
return <>{node.literal || ''}</>;
};
18 changes: 5 additions & 13 deletions apps/intake/src/pages/Review.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { EditOutlined } from '@mui/icons-material';
import { IconButton, Table, TableBody, TableCell, TableRow, Tooltip, Typography, useTheme } from '@mui/material';
import { IconButton, Table, TableBody, TableCell, TableRow, Tooltip, Typography } from '@mui/material';
import { QuestionnaireResponseItem } from 'fhir/r4b';
import { DateTime } from 'luxon';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { TermsAndConditions } from 'src/components/TermsAndConditions';
import {
APIError,
APPOINTMENT_CANT_BE_IN_PAST_ERROR,
Expand Down Expand Up @@ -43,6 +44,8 @@ const makeFullName = (patient: PatientInfo | undefined): string | undefined => {
return `${firstName}${middleName ? ` ${middleName}` : ''} ${lastName}`;
};

const PAGE_ID = 'REVIEW_PAGE';

const Review = (): JSX.Element => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
Expand All @@ -60,7 +63,6 @@ const Review = (): JSX.Element => {
completeBooking,
} = useBookingContext();
const [errorConfig, setErrorConfig] = useState<ErrorDialogConfig | undefined>(undefined);
const theme = useTheme();
const { slotId } = useParams<{ slotId: string }>();

const patientInfo: PatientInfo | undefined = (() => {
Expand Down Expand Up @@ -235,17 +237,7 @@ const Review = (): JSX.Element => {
))}
</TableBody>
</Table>
<Typography color={theme.palette.text.secondary}>
{t('reviewAndSubmit.byProceeding')}
<Link to="/template.pdf" target="_blank" data-testid={dataTestIds.privacyPolicyReviewScreen}>
{t('reviewAndSubmit.privacyPolicy')}
</Link>{' '}
{t('reviewAndSubmit.andPrivacyPolicy')}
<Link to="/template.pdf" target="_blank" data-testid={dataTestIds.termsAndConditionsReviewScreen}>
{t('reviewAndSubmit.termsAndConditions')}
</Link>
.
</Typography>
<TermsAndConditions pageId={PAGE_ID} />
<PageForm
controlButtons={useMemo(
() => ({
Expand Down
29 changes: 5 additions & 24 deletions apps/intake/src/pages/ReviewPaperwork.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { EditOutlined } from '@mui/icons-material';
import { IconButton, Link as MuiLink, Table, TableBody, TableCell, TableRow, Tooltip, Typography } from '@mui/material';
import { IconButton, Table, TableBody, TableCell, TableRow, Tooltip, Typography } from '@mui/material';
import { QuestionnaireResponseItem } from 'fhir/r4b';
import { t } from 'i18next';
import { DateTime } from 'luxon';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { TermsAndConditions } from 'src/components/TermsAndConditions';
import { makeValidationSchema, pickFirstValueFromAnswerItem, ServiceMode, uuidRegex, VisitType } from 'utils';
import { ValidationError } from 'yup';
import { dataTestIds } from '../../src/helpers/data-test-ids';
Expand All @@ -26,12 +27,12 @@ import { otherColors } from '../IntakeThemeProvider';
import i18n from '../lib/i18n';
import { useAppointmentStore } from '../telemed/features/appointments/appointment.store';
import { useCreateInviteMutation } from '../telemed/features/waiting-room';
import { useOpenExternalLink } from '../telemed/hooks/useOpenExternalLink';
import { ReviewItem } from '../types';
import { slugFromLinkId } from './PaperworkPage';

const PAGE_ID = 'PAPERWORK_REVIEW_PAGE';

const ReviewPaperwork = (): JSX.Element => {
const openExternalLink = useOpenExternalLink();
const navigate = useNavigate();
const { id: appointmentID } = useParams();
const { pathname } = useLocation();
Expand Down Expand Up @@ -377,27 +378,7 @@ const ReviewPaperwork = (): JSX.Element => {
))}
</TableBody>
</Table>
<Typography variant="body2">
By proceeding with a visit, you acknowledge that you have reviewed and accept our{' '}
<MuiLink
sx={{ cursor: 'pointer' }}
onClick={() => openExternalLink('/template.pdf')}
target="_blank"
data-testid={dataTestIds.privacyPolicyReviewScreen}
>
Privacy Policy
</MuiLink>{' '}
and{' '}
<MuiLink
sx={{ cursor: 'pointer' }}
onClick={() => openExternalLink('/template.pdf')}
target="_blank"
data-testid={dataTestIds.termsAndConditionsReviewScreen}
>
Terms and Conditions of Service
</MuiLink>
.
</Typography>
<TermsAndConditions pageId={PAGE_ID} />
<PageForm
controlButtons={{
submitLabel: submitButtonLabel,
Expand Down
30 changes: 20 additions & 10 deletions apps/intake/tests/component/ReviewPage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { DateTime } from 'luxon';
import { MemoryRouter } from 'react-router-dom';
import { VisitType } from 'utils';
import { getPrivacyPolicyLinkDefForLocation, getTermsAndConditionsLinkDefForLocation, VisitType } from 'utils';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import ReviewPage from '../../src/pages/Review';

Expand Down Expand Up @@ -99,15 +99,25 @@ describe('Review and Submit Screen', () => {
</MemoryRouter>
);

const privacyPolicyLink = screen.getByRole('link', { name: 'Privacy Policy' });
expect(privacyPolicyLink).toBeDefined();
expect(privacyPolicyLink.getAttribute('href')).toBe('/template.pdf');
expect(privacyPolicyLink.getAttribute('target')).toBe('_blank');

const termsLink = screen.getByRole('link', { name: 'Terms and Conditions of Service' });
expect(termsLink).toBeDefined();
expect(termsLink.getAttribute('href')).toBe('/template.pdf');
expect(termsLink.getAttribute('target')).toBe('_blank');
const privacyLinkDef = getPrivacyPolicyLinkDefForLocation('REVIEW_PAGE');
const privacyPolicyLink = screen.queryByRole('link', { name: 'Privacy Policy' });
if (privacyLinkDef === undefined) {
expect(privacyPolicyLink).toBeNull();
} else {
expect(privacyPolicyLink).toBeDefined();
expect(privacyPolicyLink?.getAttribute('href')).toBe(privacyLinkDef.url);
expect(privacyPolicyLink?.getAttribute('target')).toBe('_blank');
}

const termsLinkDef = getTermsAndConditionsLinkDefForLocation('REVIEW_PAGE');
const termsLink = screen.queryByRole('link', { name: 'Terms and Conditions of Service' });
if (termsLinkDef === undefined) {
expect(termsLink).toBeNull();
} else {
expect(termsLink).toBeDefined();
expect(termsLink?.getAttribute('href')).toBe(termsLinkDef.url);
expect(termsLink?.getAttribute('target')).toBe('_blank');
}
});

test('Check visit type display differences', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, Page, test } from '@playwright/test';
import { getPrivacyPolicyLinkDefForLocation, getTermsAndConditionsLinkDefForLocation } from 'utils';
import { CommonLocatorsHelper } from '../../utils/CommonLocatorsHelper';
import { PrebookInPersonFlow } from '../../utils/in-person/PrebookInPersonFlow';
import { Locators } from '../../utils/locators';
Expand All @@ -9,6 +10,7 @@ let locator: Locators;
let visitData: Awaited<ReturnType<PrebookInPersonFlow['goToReviewPage']>>;
let commonLocators: CommonLocatorsHelper;
let scheduleOwnerTypeExpected = 'Location';
const REVIEW_PAGE_ID = 'REVIEW_PAGE';

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
Expand Down Expand Up @@ -73,10 +75,22 @@ test('Review and Submit Screen check data is correct', async () => {
});

await test.step('Check privacy policy link', async () => {
await commonLocators.checkLinkOpensPdf(locator.privacyPolicyReviewScreen);
const privacyLinkDef = getPrivacyPolicyLinkDefForLocation(REVIEW_PAGE_ID);
if (privacyLinkDef === undefined) {
await expect(locator.privacyPolicyReviewScreen).not.toBeVisible();
return;
}
const link = page.locator(`[data-testid="${privacyLinkDef.testId}"]`);
await commonLocators.checkLinkOpensPdf(link);
});

await test.step('Check terms and conditions link', async () => {
await commonLocators.checkLinkOpensPdf(locator.termsAndConditions);
const termsLinkDef = getTermsAndConditionsLinkDefForLocation(REVIEW_PAGE_ID);
if (termsLinkDef === undefined) {
await expect(locator.termsAndConditions).not.toBeVisible();
return;
}
const link = page.locator(`[data-testid="${termsLinkDef.testId}"]`);
await commonLocators.checkLinkOpensPdf(link);
});
});
24 changes: 21 additions & 3 deletions apps/intake/tests/specs/in-person/PaperworkReviewScreen.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// cSpell:ignore networkidle
import { BrowserContext, expect, Page, test } from '@playwright/test';
import { chooseJson, CreateAppointmentResponse } from 'utils';
import {
chooseJson,
CreateAppointmentResponse,
getPrivacyPolicyLinkDefForLocation,
getTermsAndConditionsLinkDefForLocation,
} from 'utils';
import { CommonLocatorsHelper } from '../../utils/CommonLocatorsHelper';
import { PrebookInPersonFlow } from '../../utils/in-person/PrebookInPersonFlow';
import { Locators } from '../../utils/locators';
Expand All @@ -16,6 +21,7 @@ let locator: Locators;
let uploadPhoto: UploadDocs;
let commonLocators: CommonLocatorsHelper;
const appointmentIds: string[] = [];
const REVIEW_PAGE_ID = 'PAPERWORK_REVIEW_PAGE';

test.beforeAll(async ({ browser }) => {
context = await browser.newContext();
Expand Down Expand Up @@ -111,10 +117,22 @@ test.describe('Paperwork.Review and Submit - Check values', () => {
await expect(locator.checkInTimePaperworkReviewScreen).toHaveText(`${bookingData.selectedSlot}`);
});
test('PRS-9 Check privacy policy link', async () => {
await commonLocators.checkLinkOpensPdf(locator.privacyPolicyReviewScreen);
const privacyLinkDef = getPrivacyPolicyLinkDefForLocation(REVIEW_PAGE_ID);
if (privacyLinkDef === undefined) {
await expect(locator.privacyPolicyReviewScreen).not.toBeVisible();
return;
}
const link = page.locator(`[data-testid="${privacyLinkDef.testId}"]`);
await commonLocators.checkLinkOpensPdf(link);
});
test('PRS-10 Check terms and conditions link', async () => {
await commonLocators.checkLinkOpensPdf(locator.termsAndConditions);
const termsLinkDef = getTermsAndConditionsLinkDefForLocation(REVIEW_PAGE_ID);
if (termsLinkDef === undefined) {
await expect(locator.termsAndConditions).not.toBeVisible();
return;
}
const link = page.locator(`[data-testid="${termsLinkDef.testId}"]`);
await commonLocators.checkLinkOpensPdf(link);
});
});
test.describe('Paperwork.Review and Submit - Check edit icons', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// cSpell:ignore networkidle, PRST
import { BrowserContext, expect, Page, test } from '@playwright/test';
import { chooseJson, CreateAppointmentResponse } from 'utils';
import {
chooseJson,
CreateAppointmentResponse,
getPrivacyPolicyLinkDefForLocation,
getTermsAndConditionsLinkDefForLocation,
} from 'utils';
import { CommonLocatorsHelper } from '../../utils/CommonLocatorsHelper';
import { Locators } from '../../utils/locators';
import { Paperwork } from '../../utils/Paperwork';
Expand Down Expand Up @@ -39,6 +44,7 @@ test.afterAll(async () => {
await page.close();
await context.close();
});
const REVIEW_PAGE_ID = 'REVIEW_PAGE';

test.describe('Paperwork.Review and Submit - Check Complete/Missing chips', () => {
test.describe.configure({ mode: 'serial' });
Expand Down Expand Up @@ -127,10 +133,22 @@ test.describe('Paperwork.Review and Submit - Check values', () => {
);
});
test('PRST-9 Check privacy policy link', async () => {
await commonLocatorsHelper.checkLinkOpensPdf(locator.privacyPolicyReviewScreen);
const privacyLinkDef = getPrivacyPolicyLinkDefForLocation(REVIEW_PAGE_ID);
if (privacyLinkDef === undefined) {
await expect(locator.privacyPolicyReviewScreen).not.toBeVisible();
return;
}
const link = page.locator(`[data-testid="${privacyLinkDef.testId}"]`);
await commonLocatorsHelper.checkLinkOpensPdf(link);
});
test('PRST-10 Check terms and conditions link', async () => {
await commonLocatorsHelper.checkLinkOpensPdf(locator.termsAndConditions);
const termsLinkDef = getTermsAndConditionsLinkDefForLocation(REVIEW_PAGE_ID);
if (termsLinkDef === undefined) {
await expect(locator.termsAndConditions).not.toBeVisible();
return;
}
const link = page.locator(`[data-testid="${termsLinkDef.testId}"]`);
await commonLocatorsHelper.checkLinkOpensPdf(link);
});
});
test.describe('Paperwork.Review and Submit - Check edit icons', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/utils/.ottehr_config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './examination';
export * from './screening-questions';
export * from './texting';
export * from './vitals';
export * from './legal';
1 change: 1 addition & 0 deletions packages/utils/.ottehr_config/legal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const LEGAL_OVERRIDES = {};
1 change: 1 addition & 0 deletions packages/utils/lib/configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './examination';
export * from './questionnaire';
export * from './texting';
export * from './vitals';
export * from './legal';
Loading
Loading