├── screenshot.png ├── src ├── pages │ ├── MessageTablePage.module.css │ ├── SignOutPage.tsx │ ├── ObservationPage.tsx │ ├── health-record │ │ ├── LabResult.tsx │ │ ├── Vaccine.tsx │ │ ├── Vitals.tsx │ │ ├── index.tsx │ │ ├── LabResults.tsx │ │ ├── Medications.tsx │ │ ├── Responses.tsx │ │ ├── Response.tsx │ │ ├── Medication.tsx │ │ ├── Measurement.data.ts │ │ ├── Vaccines.tsx │ │ └── Measurement.tsx │ ├── care-plan │ │ ├── index.tsx │ │ ├── ActionItem.tsx │ │ └── ActionItems.tsx │ ├── account │ │ ├── index.tsx │ │ ├── Provider.tsx │ │ ├── MembershipAndBilling.tsx │ │ └── Profile.tsx │ ├── SignInPage.tsx │ ├── RegisterPage.tsx │ ├── GetCarePage.tsx │ ├── HomePage.module.css │ ├── MessageTablePage.tsx │ ├── landing │ │ ├── Header.module.css │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── Header.tsx │ ├── QuestionnairePage.tsx │ ├── MessagesPage.tsx │ ├── HomePage.tsx │ └── PatientIntakeQuestionnairePage.tsx ├── img │ ├── favicon.ico │ ├── homePage │ │ ├── people-talk.jpg │ │ ├── health-visit.jpg │ │ ├── hero-background.jpg │ │ ├── task-icon.svg │ │ ├── medplum.svg │ │ ├── health-record.svg │ │ ├── pill.svg │ │ ├── better-sleep.svg │ │ ├── doctor.svg │ │ └── pharmacy.svg │ ├── landingPage │ │ ├── doctor.jpg │ │ ├── laboratory.jpg │ │ ├── engineering.jpg │ │ └── working-environment.jpg │ ├── pills.svg │ └── avatar-placeholder.svg ├── vite-env.d.ts ├── components │ ├── Footer.module.css │ ├── Loading.tsx │ ├── InfoSection.module.css │ ├── InfoButton.module.css │ ├── InfoButton.tsx │ ├── InfoSection.tsx │ ├── SideMenu.module.css │ ├── Footer.tsx │ ├── LineChart.tsx │ ├── SideMenu.tsx │ ├── Header.module.css │ ├── Header.tsx │ └── Logo.tsx ├── config.ts ├── App.test.tsx ├── test.setup.ts ├── App.tsx ├── main.tsx └── Router.tsx ├── .gitattributes ├── babel.config.json ├── vercel.json ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.json ├── postcss.config.mjs ├── jest.config.json ├── package.json ├── README.md └── LICENSE.txt /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/screenshot.png -------------------------------------------------------------------------------- /src/pages/MessageTablePage.module.css: -------------------------------------------------------------------------------- 1 | .tableBody { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/favicon.ico -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | package-lock.json -diff 3 | package-lock.json linguist-generated=true 4 | -------------------------------------------------------------------------------- /src/img/homePage/people-talk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/homePage/people-talk.jpg -------------------------------------------------------------------------------- /src/img/landingPage/doctor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/landingPage/doctor.jpg -------------------------------------------------------------------------------- /src/img/homePage/health-visit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/homePage/health-visit.jpg -------------------------------------------------------------------------------- /src/img/landingPage/laboratory.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/landingPage/laboratory.jpg -------------------------------------------------------------------------------- /src/img/homePage/hero-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/homePage/hero-background.jpg -------------------------------------------------------------------------------- /src/img/landingPage/engineering.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/landingPage/engineering.jpg -------------------------------------------------------------------------------- /src/img/landingPage/working-environment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medplum/foomedical/HEAD/src/img/landingPage/working-environment.jpg -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", ["@babel/preset-react", { "runtime": "automatic" }], "@babel/preset-typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "installCommand": "npm install", 3 | "buildCommand": "npm run build", 4 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] 5 | } 6 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | /// 4 | -------------------------------------------------------------------------------- /src/components/Footer.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-8)); 3 | } 4 | 5 | .inner { 6 | background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-8)); 7 | border-top: 1px solid var(--mantine-color-gray-2); 8 | padding: var(--mantine-spacing-xl); 9 | text-align: center; 10 | } 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import react from '@vitejs/plugin-react'; 4 | import { defineConfig } from 'vite'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [react()], 9 | server: { 10 | port: 3000, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .env 12 | dist 13 | dist-ssr 14 | *.local 15 | coverage 16 | package-lock.json 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Center, Loader } from '@mantine/core'; 4 | import type { JSX } from 'react'; 5 | 6 | export function Loading(): JSX.Element { 7 | return ( 8 |
9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/InfoSection.module.css: -------------------------------------------------------------------------------- 1 | .titleSection { 2 | background: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-6)); 3 | border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-1)); 4 | color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-1)); 5 | padding: var(--mantine-spacing-md) var(--mantine-spacing-md); 6 | } 7 | 8 | .title { 9 | font-weight: 500; 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Foo Medical 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/img/homePage/task-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Default values work on localhost:3000 and foomedical.com 5 | // Replace these values with your own values for production 6 | export const MEDPLUM_PROJECT_ID = '9602358d-eeb0-4de8-bccf-e2438b5c9162'; 7 | export const MEDPLUM_GOOGLE_CLIENT_ID = '679052511930-8dqur4mmg8egbttgos5pmr4ljtf3etbb.apps.googleusercontent.com'; 8 | export const MEDPLUM_RECAPTCHA_SITE_KEY = '6LfFd_8gAAAAAOCVrZQ_aF2CN5b7s91NEYIu5GxL'; 9 | -------------------------------------------------------------------------------- /src/pages/SignOutPage.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { useMedplum } from '@medplum/react'; 4 | import { useEffect } from 'react'; 5 | 6 | export function SignOutPage(): null { 7 | const medplum = useMedplum(); 8 | 9 | useEffect(() => { 10 | medplum 11 | .signOut() 12 | .then(() => { 13 | window.location.href = '/'; 14 | }) 15 | .catch(console.error); 16 | }, [medplum]); 17 | 18 | return null; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/InfoButton.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1)); 3 | padding: var(--mantine-spacing-md) var(--mantine-spacing-md); 4 | 5 | &:not(:last-child) { 6 | border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-1)); 7 | } 8 | 9 | &:hover { 10 | background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6)); 11 | color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx" 17 | }, 18 | "exclude": ["dist"] 19 | } 20 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import mantinePreset from 'postcss-preset-mantine'; 4 | import simpleVars from 'postcss-simple-vars'; 5 | 6 | const config = { 7 | plugins: [ 8 | mantinePreset(), 9 | simpleVars({ 10 | variables: { 11 | 'mantine-breakpoint-xs': '36em', 12 | 'mantine-breakpoint-sm': '48em', 13 | 'mantine-breakpoint-md': '62em', 14 | 'mantine-breakpoint-lg': '75em', 15 | 'mantine-breakpoint-xl': '88em', 16 | }, 17 | }), 18 | ], 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /src/components/InfoButton.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Group, UnstyledButton } from '@mantine/core'; 4 | import type { JSX, ReactNode } from 'react'; 5 | import classes from './InfoButton.module.css'; 6 | 7 | export interface InfoButtonProps { 8 | readonly onClick?: () => void; 9 | readonly children: ReactNode; 10 | } 11 | 12 | export function InfoButton(props: InfoButtonProps): JSX.Element { 13 | return ( 14 | 15 | {props.children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testEnvironment": "jsdom", 3 | "testTimeout": 120000, 4 | "transform": { 5 | "^.+\\.(js|jsx|ts|tsx)$": "babel-jest", 6 | ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub" 7 | }, 8 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 9 | "moduleNameMapper": { 10 | "^.+.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub" 11 | }, 12 | "testMatch": ["**/src/**/*.test.ts", "**/src/**/*.test.tsx"], 13 | "setupFilesAfterEnv": ["./src/test.setup.ts"], 14 | "coverageDirectory": "coverage", 15 | "coverageReporters": ["json", "text", "lcov"], 16 | "collectCoverageFrom": ["**/src/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/ObservationPage.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Title } from '@mantine/core'; 4 | import { Document, ResourceTable, useMedplum } from '@medplum/react'; 5 | import type { JSX } from 'react'; 6 | import { useParams } from 'react-router'; 7 | 8 | export function ObservationPage(): JSX.Element { 9 | const medplum = useMedplum(); 10 | const { observationId = '' } = useParams(); 11 | const resource = medplum.readResource('Observation', observationId).read(); 12 | 13 | return ( 14 | 15 | Observation 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { MantineProvider } from '@mantine/core'; 4 | import { MockClient } from '@medplum/mock'; 5 | import { MedplumProvider } from '@medplum/react'; 6 | import { act, render } from '@testing-library/react'; 7 | import { MemoryRouter } from 'react-router'; 8 | import { App } from './App'; 9 | 10 | test('App renders', async () => { 11 | await act(async () => { 12 | render( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/pages/health-record/LabResult.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Box } from '@mantine/core'; 4 | import type { DiagnosticReport } from '@medplum/fhirtypes'; 5 | import { DiagnosticReportDisplay, useMedplum } from '@medplum/react'; 6 | import type { JSX } from 'react'; 7 | import { useParams } from 'react-router'; 8 | import { InfoSection } from '../../components/InfoSection'; 9 | 10 | export function LabResult(): JSX.Element { 11 | const medplum = useMedplum(); 12 | const { resultId = '' } = useParams(); 13 | const resource: DiagnosticReport = medplum.readResource('DiagnosticReport', resultId).read(); 14 | 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/care-plan/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Container, Group } from '@mantine/core'; 4 | import { Suspense } from 'react'; 5 | import type { JSX } from 'react'; 6 | import { Outlet } from 'react-router'; 7 | import { Loading } from '../../components/Loading'; 8 | import { SideMenu } from '../../components/SideMenu'; 9 | 10 | const sideMenu = { 11 | title: 'Care Plan', 12 | menu: [{ name: 'Action Items', href: '/care-plan/action-items' }], 13 | }; 14 | 15 | export function CarePlanPage(): JSX.Element { 16 | return ( 17 | 18 | 19 | 20 |
21 | }> 22 | 23 | 24 |
25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/care-plan/ActionItem.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Box, Title } from '@mantine/core'; 4 | import type { CarePlan } from '@medplum/fhirtypes'; 5 | import { ResourceTable, useMedplum } from '@medplum/react'; 6 | import type { JSX } from 'react'; 7 | import { useParams } from 'react-router'; 8 | import { InfoSection } from '../../components/InfoSection'; 9 | 10 | export function ActionItem(): JSX.Element { 11 | const medplum = useMedplum(); 12 | const { itemId } = useParams(); 13 | const resource: CarePlan = medplum.readResource('CarePlan', itemId as string).read(); 14 | 15 | return ( 16 | 17 | 18 | {resource.title} 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/test.setup.ts: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import '@testing-library/jest-dom'; 4 | import { TextDecoder, TextEncoder } from 'node:util'; 5 | 6 | Object.defineProperty(globalThis.window, 'TextDecoder', { value: TextDecoder }); 7 | Object.defineProperty(globalThis.window, 'TextEncoder', { value: TextEncoder }); 8 | 9 | Object.defineProperty(window, 'matchMedia', { 10 | writable: true, 11 | value: jest.fn().mockImplementation((query) => ({ 12 | matches: false, 13 | media: query, 14 | onchange: null, 15 | addListener: jest.fn(), 16 | removeListener: jest.fn(), 17 | addEventListener: jest.fn(), 18 | removeEventListener: jest.fn(), 19 | dispatchEvent: jest.fn(), 20 | })), 21 | }); 22 | 23 | class ResizeObserver { 24 | observe(): void {} 25 | unobserve(): void {} 26 | disconnect(): void {} 27 | } 28 | 29 | window.ResizeObserver = ResizeObserver; 30 | -------------------------------------------------------------------------------- /src/pages/health-record/Vaccine.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Box, Title } from '@mantine/core'; 4 | import type { Immunization } from '@medplum/fhirtypes'; 5 | import { ResourceTable, useMedplum } from '@medplum/react'; 6 | import type { JSX } from 'react'; 7 | import { useParams } from 'react-router'; 8 | import { InfoSection } from '../../components/InfoSection'; 9 | 10 | export function Vaccine(): JSX.Element { 11 | const medplum = useMedplum(); 12 | const { vaccineId = '' } = useParams(); 13 | const vaccine: Immunization = medplum.readResource('Immunization', vaccineId).read(); 14 | 15 | return ( 16 | 17 | 18 | {vaccine.vaccineCode?.text} 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/account/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Container, Group } from '@mantine/core'; 4 | import { Suspense } from 'react'; 5 | import type { JSX } from 'react'; 6 | import { Outlet } from 'react-router'; 7 | import { SideMenu } from '../../components/SideMenu'; 8 | 9 | const sideMenu = { 10 | title: 'Account', 11 | menu: [ 12 | { name: 'Profile', href: '/account/profile' }, 13 | { name: 'Provider', href: '/account/provider' }, 14 | { name: 'Membership & Billing', href: '/account/membership-and-billing' }, 15 | ], 16 | }; 17 | 18 | export function AccountPage(): JSX.Element { 19 | return ( 20 | 21 | 22 | 23 |
24 | Loading...
}> 25 | 26 | 27 | 28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/SignInPage.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { BackgroundImage, Box, SimpleGrid } from '@mantine/core'; 4 | import { SignInForm } from '@medplum/react'; 5 | import type { JSX } from 'react'; 6 | import { useNavigate } from 'react-router'; 7 | import { MEDPLUM_GOOGLE_CLIENT_ID, MEDPLUM_PROJECT_ID } from '../config'; 8 | 9 | export function SignInPage(): JSX.Element { 10 | const navigate = useNavigate(); 11 | return ( 12 | 13 | 14 | navigate('/')?.catch(console.error)} 18 | > 19 |

Sign in to Foo Medical

20 |
21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/InfoSection.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Card, CloseButton, Title } from '@mantine/core'; 4 | import type { JSX, ReactNode } from 'react'; 5 | import classes from './InfoSection.module.css'; 6 | 7 | interface InfoSectionProps { 8 | readonly title?: string | JSX.Element; 9 | readonly children: ReactNode; 10 | readonly onButtonClick?: (id: string) => void; 11 | readonly resourceType?: string; 12 | readonly id?: string; 13 | } 14 | 15 | export function InfoSection({ title, children, onButtonClick, id = '' }: InfoSectionProps): JSX.Element { 16 | return ( 17 | 18 | {title && ( 19 | 20 | 21 | {title} 22 | 23 | {onButtonClick && onButtonClick(id)} />} 24 | 25 | )} 26 | {children} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/SideMenu.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | flex: 200; 3 | width: 200px; 4 | padding-top: 32px; 5 | } 6 | 7 | .title { 8 | font-weight: 500; 9 | margin-bottom: 8px; 10 | } 11 | 12 | .link { 13 | display: flex; 14 | align-items: center; 15 | text-decoration: none; 16 | font-size: var(--mantine-font-size-sm); 17 | color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1)); 18 | padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm); 19 | border-radius: var(--mantine-radius-sm); 20 | font-weight: 500; 21 | 22 | &:hover { 23 | background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8)); 24 | color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); 25 | } 26 | } 27 | 28 | .linkIcon { 29 | color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2)); 30 | margin-right: var(--mantine-spacing-sm); 31 | } 32 | 33 | .linkActive, 34 | .linkActive:hover { 35 | background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-7)); 36 | color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/RegisterPage.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { BackgroundImage, Box, SimpleGrid } from '@mantine/core'; 4 | import { RegisterForm } from '@medplum/react'; 5 | import type { JSX } from 'react'; 6 | import { useNavigate } from 'react-router'; 7 | import { MEDPLUM_GOOGLE_CLIENT_ID, MEDPLUM_PROJECT_ID, MEDPLUM_RECAPTCHA_SITE_KEY } from '../config'; 8 | 9 | export function RegisterPage(): JSX.Element { 10 | const navigate = useNavigate(); 11 | return ( 12 | 13 | 14 | navigate('/')?.catch(console.error)} 20 | > 21 |

Register with Foo Medical

22 |
23 |
24 | 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/img/homePage/medplum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Anchor, Container, Divider, SimpleGrid, Stack, Text } from '@mantine/core'; 4 | import type { JSX } from 'react'; 5 | import classes from './Footer.module.css'; 6 | 7 | export function Footer(): JSX.Element { 8 | return ( 9 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/LineChart.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import type { ChartData } from 'chart.js'; 4 | import { lazy, Suspense } from 'react'; 5 | import type { JSX } from 'react'; 6 | 7 | const lineChartOptions = { 8 | responsive: true, 9 | scales: { 10 | y: { 11 | min: 0, 12 | }, 13 | }, 14 | plugins: { 15 | legend: { 16 | position: 'bottom' as const, 17 | }, 18 | }, 19 | }; 20 | 21 | interface LineChartProps { 22 | readonly chartData: ChartData<'line', number[]>; 23 | } 24 | 25 | const AsyncLine = lazy(async () => { 26 | const { CategoryScale, Chart, Legend, LinearScale, LineElement, PointElement, Title, Tooltip } = 27 | await import('chart.js'); 28 | Chart.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); 29 | const { Line } = await import('react-chartjs-2'); 30 | return { default: Line }; 31 | }); 32 | 33 | export function LineChart({ chartData }: LineChartProps): JSX.Element { 34 | return ( 35 |
36 | Loading...
}> 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/pages/GetCarePage.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import type { Schedule } from '@medplum/fhirtypes'; 4 | import { Document, Scheduler, useMedplum } from '@medplum/react'; 5 | import type { JSX } from 'react'; 6 | 7 | export function GetCare(): JSX.Element { 8 | const medplum = useMedplum(); 9 | const schedule = medplum.searchOne('Schedule').read(); 10 | 11 | return ( 12 | 13 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/pages/health-record/Vitals.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Table, Title } from '@mantine/core'; 4 | import { formatDate, formatObservationValue, getReferenceString } from '@medplum/core'; 5 | import type { Patient } from '@medplum/fhirtypes'; 6 | import { Document, useMedplum } from '@medplum/react'; 7 | import type { JSX } from 'react'; 8 | 9 | export function Vitals(): JSX.Element { 10 | const medplum = useMedplum(); 11 | const patient = medplum.getProfile() as Patient; 12 | const observations = medplum.searchResources('Observation', 'patient=' + getReferenceString(patient)).read(); 13 | 14 | return ( 15 | 16 | Vitals 17 | 18 | 19 | 20 | Measurement 21 | Your Value 22 | Last Updated 23 | 24 | 25 | 26 | {observations.map((obs) => ( 27 | 28 | {obs.code?.coding?.[0]?.display} 29 | {formatObservationValue(obs)} 30 | {formatDate(obs.meta?.lastUpdated)} 31 | 32 | ))} 33 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/account/Provider.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Box, Button, Stack, Title } from '@mantine/core'; 4 | import type { Patient } from '@medplum/fhirtypes'; 5 | import { ResourceAvatar, ResourceName, useMedplum } from '@medplum/react'; 6 | import type { JSX } from 'react'; 7 | import { InfoSection } from '../../components/InfoSection'; 8 | 9 | export function Provider(): JSX.Element { 10 | const medplum = useMedplum(); 11 | const patient = medplum.getProfile() as Patient; 12 | 13 | if (patient.generalPractitioner && patient.generalPractitioner.length > 0) { 14 | return ( 15 | 16 | My Provider 17 | 18 | 19 | 20 | 21 | 22 | <ResourceName value={patient.generalPractitioner[0]} /> 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | 32 | return ( 33 | 34 | Choose a provider 35 | TODO 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/SideMenu.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Title } from '@mantine/core'; 4 | import cx from 'clsx'; 5 | import { Fragment } from 'react'; 6 | import type { JSX } from 'react'; 7 | import { NavLink } from 'react-router'; 8 | import classes from './SideMenu.module.css'; 9 | 10 | export interface SubMenuProps { 11 | readonly name: string; 12 | readonly href: string; 13 | } 14 | 15 | export interface SideMenuProps { 16 | readonly title: string; 17 | readonly menu: { name: string; href: string; subMenu?: SubMenuProps[] }[]; 18 | } 19 | 20 | export function SideMenu(props: SideMenuProps): JSX.Element { 21 | return ( 22 |
23 | 24 | {props.title} 25 | 26 | {props.menu.map((item) => ( 27 | 28 | cx(classes.link, isActive && classes.linkActive)}> 29 | {item.name} 30 | 31 | {item.subMenu?.map((subItem) => ( 32 |
33 | 34 | {subItem.name} 35 | 36 |
37 | ))} 38 |
39 | ))} 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/health-record/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { Container, Group } from '@mantine/core'; 4 | import { Suspense } from 'react'; 5 | import type { JSX } from 'react'; 6 | import { Outlet } from 'react-router'; 7 | import { Loading } from '../../components/Loading'; 8 | import { SideMenu } from '../../components/SideMenu'; 9 | import { measurementsMeta } from './Measurement.data'; 10 | 11 | const sideMenu = { 12 | title: 'Health Record', 13 | menu: [ 14 | { name: 'Lab Results', href: '/health-record/lab-results' }, 15 | { name: 'Medications', href: '/health-record/medications' }, 16 | { name: 'Questionnaire Responses', href: '/health-record/questionnaire-responses' }, 17 | { name: 'Vaccines', href: '/health-record/vaccines' }, 18 | { 19 | name: 'Vitals', 20 | href: '/health-record/vitals', 21 | subMenu: Object.values(measurementsMeta).map(({ title, id }) => ({ 22 | name: title, 23 | href: `/health-record/vitals/${id}`, 24 | })), 25 | }, 26 | ], 27 | }; 28 | 29 | export function HealthRecord(): JSX.Element { 30 | return ( 31 | 32 | 33 | 34 |
35 | }> 36 | 37 | 38 |
39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | import { AppShell } from '@mantine/core'; 4 | import { ErrorBoundary, useMedplum } from '@medplum/react'; 5 | import { Suspense } from 'react'; 6 | import type { JSX } from 'react'; 7 | import { Navigate, Route, Routes } from 'react-router'; 8 | import { Router } from './Router'; 9 | import { Footer } from './components/Footer'; 10 | import { Header } from './components/Header'; 11 | import { Loading } from './components/Loading'; 12 | import { RegisterPage } from './pages/RegisterPage'; 13 | import { SignInPage } from './pages/SignInPage'; 14 | import { LandingPage } from './pages/landing'; 15 | 16 | export function App(): JSX.Element | null { 17 | const medplum = useMedplum(); 18 | 19 | if (medplum.isLoading()) { 20 | return null; 21 | } 22 | 23 | if (!medplum.getProfile()) { 24 | return ( 25 | 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | 31 | ); 32 | } 33 | 34 | return ( 35 | 36 |
37 | 38 | 39 | }> 40 | 41 | 42 | 43 | 44 |