Skip to content

Commit aaa040f

Browse files
committed
fix merge conflict
2 parents 46d4ba2 + 315e298 commit aaa040f

15 files changed

+203
-139
lines changed

README.md

+73-21
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,86 @@
1-
# OSCEBossKey
1+
# O S C E B O S S K E Y
22

3-
[![Build Status](https://travis-ci.org/fac-14/OSCEBossKey.svg?branch=master)](https://travis-ci.org/fac-14/OSCEBossKey)
3+
[![Build Status](https://travis-ci.org/fac-14/OSCEBossKey.svg?branch=master)](https://travis-ci.org/fac-14/OSCEBossKey) [![codecov](https://codecov.io/gh/fac-14/OSCEBossKey/branch/master/graph/badge.svg)](https://codecov.io/gh/fac-14/OSCEBossKey)
44

5-
[![codecov](https://codecov.io/gh/fac-14/OSCEBossKey/branch/master/graph/badge.svg)](https://codecov.io/gh/fac-14/OSCEBossKey)
5+
:sparkles: **[VIEW THE APP](https://oscebosskey.herokuapp.com/)** :sparkles:
66

7-
Weeks 13-16 > Tech for Better project
8-
## [Run in the browser](https://oscebosskey.herokuapp.com)
7+
## Doctor doctor, I think I'm a bell?
98

10-
## Instructions to run the program locally
9+
:ambulance: Let's see if we can't give this a ring, then.
1110

12-
**Install Node.js and NPM**
13-
Follow the instructions in [this article](https://blog.teamtreehouse.com/install-node-js-npm-mac) to complete the installation.
11+
We wanted to make an **OSCE revision app** using the [JAMstack](https://jamstack.org/)...
1412

15-
**Open Terminal and navigate to the folder in which you want to save the repo**
13+
... that's **responsive**, **mobile first** and features **swipe functionality**\
14+
... that's rendered in **React** on the client side\
15+
... that's styled with **SASS** and **styled-components**\
16+
... that uses an **Express proxy server** to route our **Airtable** requests and hide our precious API key\
17+
... that we can deploy on **Heroku**\
18+
... with unit and integration tests with **Jest** & **React Testing Library**
1619

17-
**Download a local version of the repo**
18-
`git clone https://github.com/fac-14/OSCEBossKey.git`
20+
## Wait, what's an OSCE?
1921

20-
**Navigate to the downloaded repo**
21-
`cd OSCEBossKey`
22+
"An objective structured clinical examination (OSCE) is a modern type of examination often used in health sciences," says Wikipedia. It is a hands-on, real-world test that, in short, has actors roleplay a medical condition while a medical student looks to diagnose. Pretty cool!
2223

23-
**Install the dependencies needed to run the server**
24-
`npm i` (this could take a couple of minutes depending on internet connection)
24+
## Our Tech Stack
2525

26-
**Boot up the server**
27-
`npm start` (this could take a few seconds)
26+
:love_letter: React (with React Router, HammerJS and Styled Components)\
27+
:information_desk_person: Express\
28+
:gift: Parcel\
29+
:heart_eyes_cat: Babel\
30+
:books: Airtable\
31+
:relieved: Jest, React Testing Library, Supertest and Codecov\
32+
:bulb: ESLint / Prettier\
33+
:family: Eve, Martin, Monika & Nathalie
2834

29-
**Navigate in your browser to the port shown in your Terminal**
30-
URL: `localhost:3333`
35+
## How do I run this code locally?
3136

32-
![mindblown](https://media.giphy.com/media/xT0xeJpnrWC4XWblEk/giphy-downsized-large.gif)
37+
**Prerequisites:** Node.js, NPM and no allergies to terminals.
3338

34-
Don't worry if you have any problems with this, we can fix any problems on Friday.
39+
```bash
40+
$ git clone https://github.com/fac-14/OSCEBossKey.git
41+
$ cd OSCEBossKey
42+
$ npm i # this will likely take a couple of minutes
43+
$ echo "AIRTABLE_API_KEY=the_actual_key_here" >> .env
44+
$ npm test # this will make sure you're all setup and good to go
45+
$ npm run
46+
```
47+
48+
Then point your browser to `localhost:3333` (or the port the server says in your terminal) and you're golden :+1:
49+
50+
## Functionality
51+
52+
The app is divided into four main sections, with our work in this sprint being on creating a full user journey across the **History** section. Here's what they do:
53+
54+
1. **History: A journey where an actor assumes the role of a patient with a medical history - such as a 52-year-old heavy smoker with chest pain.** _(Our focus for this sprint!)_
55+
2. **Examinations**: A medical student would follow a procedure without the associated history, such as investigating a hip or ankle.
56+
3. **Extras**: Additional information and reference about common procedures, such as inhaler technique.
57+
4. **Stats**: Data visualisations of user journey over time, ultimately to show students which areas they are doing well in and areas that need more attention.
58+
59+
Our _History_ section allows users to either study from a pre-created list, or add (and then revise) their own cases to the list. Swipeable screens allow the actor taking on the role of the patient to understand their medical history while the medical student diagnoses, as well as easily check off the things they observe the medical student doing.
60+
61+
All in all, much easier than lugging around a backpack of heavy textbooks!
62+
63+
## Some of the nicest bits
64+
65+
- **Resuable Components**: We are obsessed by code drier than Jacob's Cream Crackers, so being able to re-use as much as possible - such as in our [api calls](https://github.com/fac-14/OSCEBossKey/blob/2184ff3510355e5429940e707b44a5ac32f85ee9/src/app.js#L29-L41) and [category listing component](https://github.com/fac-14/OSCEBossKey/blob/2184ff3510355e5429940e707b44a5ac32f85ee9/src/components/CategoryListing.js#L76-L85) - gives us major warm fuzzies.
66+
- **Pure Utility Functions**: We :heart: React, but we also like keeping our functions pure - so we created our [utility functions as testable pure functions](https://github.com/fac-14/OSCEBossKey/tree/master/src/utils) and then imported them into React as necessary.
67+
- **TDD API calls**: We built our [API responses up with pure TDD](https://github.com/fac-14/OSCEBossKey/blob/master/__test__/app.test.js), which was totally :ok_hand: because they're rock-solid and we can trust they work.
68+
- **Swipe Right (& Left)**: Our user journeys are mobile-focused, where swipe actions are a normal user behaviour. But browsers still have a bit of a tricky time making them work. We researched a _lot_ of ways around this, and ended up using HammerJS because it had a small footprint, [easy implementation](https://github.com/fac-14/OSCEBossKey/blob/2184ff3510355e5429940e707b44a5ac32f85ee9/src/components/Pages/Revision/RevisionContainer.js#L61-L73) and the functionality we needed.
69+
70+
## Prototyping
71+
72+
As we were a pair-programming duo & a keen PO, we definitely took time to **prototype** everything out in Figma to make sure we were aligned and we could focus on implementing the code in a clean, efficient way - because it would be a little frustrating to realise you need to make an all-important button from scratch because you forgot to plan for it :scream:
73+
74+
Our Figma prototypes served us really well in testing our user journey until we were at a stage where our app got to the point it was able to be demonstrated. Here's how it looked:
75+
76+
![Our prototyping in action...](https://i.imgur.com/YGTdLUM.png)
77+
78+
## And the next sprint...
79+
80+
We are **super proud** of the work we managed to do in a single sprint, but there's obviously loads of stuff we weren't able to include. We think we've got a totally kickass MVP but if we had more time we'd look at:
81+
82+
1. Coding out the Examinations and Extras sections - we have basic support in now, but this could be expanded on for sure
83+
2. More tests! Test ALL THE THINGS! We think we've done a good job here but we're always striving for the best.
84+
3. Creating a robust authentication system (this is an entire sprint of its own)
85+
4. With proper user support, we'd love to make a really awesome statistics page - detailing things like average score and most missed marks
86+
5. Transform our potent webapp into a React Native delight

src/components/Cases/Title.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
import React from "react";
44
import PropTypes from "prop-types";
55

6-
export default class CasesPageTitle extends React.Component {
7-
render() {
8-
return <h1 id="title">{this.props.stationName}</h1>;
9-
}
10-
}
6+
const CasesPageTitle = props => <h1 id="title">{props.stationName}</h1>;
117

128
CasesPageTitle.propTypes = {
139
stationName: PropTypes.string
1410
};
11+
12+
export default CasesPageTitle;

src/components/Pages/AddNew/AddNewBanner.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,16 @@ const StyledCase = styled.div`
1717
border: none;
1818
`;
1919

20-
export default class AddNewBanner extends React.Component {
21-
render() {
22-
return (
23-
<Link to={`/${this.props.exam}/${this.props.station}/add-case`}>
24-
<StyledCase onClick={this.props.submitStation}> Add New</StyledCase>
25-
</Link>
26-
);
27-
}
28-
}
20+
const AddNewBanner = props => (
21+
<Link to={`/${props.exam}/${props.station}/add-case`}>
22+
<StyledCase onClick={props.submitStation}> Add New</StyledCase>
23+
</Link>
24+
);
2925

3026
AddNewBanner.propTypes = {
3127
exam: PropTypes.string,
3228
station: PropTypes.string,
3329
submitStation: PropTypes.func
3430
};
31+
32+
export default AddNewBanner;

src/components/Pages/AddNew/AddNewContainer.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,11 @@ export default class AddNewContainer extends React.Component {
9595
};
9696

9797
handleSubmit = event => {
98+
const { newMarkSchemeElement } = this.state;
9899
event.preventDefault();
99100
event.target["new-mark-scheme-element"].value = "";
100-
this.props.newMarkSchemeElement(this.state.newMarkSchemeElement);
101+
if (newMarkSchemeElement)
102+
this.props.newMarkSchemeElement(newMarkSchemeElement);
101103
};
102104

103105
render() {
@@ -147,7 +149,9 @@ export default class AddNewContainer extends React.Component {
147149
placeholder="Add new..."
148150
onChange={this.handleChange}
149151
/>
150-
<StyledButton type="submit" value="&#43;" />
152+
{this.state.newMarkSchemeElement && (
153+
<StyledButton type="submit" value="&#43;" />
154+
)}
151155
</StyledForm>
152156
<MarkSchemeList
153157
id="add-new-list"

src/components/Pages/AddNew/AddNewTile.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
import React from "react";
44
import { Link } from "react-router-dom";
55

6-
export default class AddNewTile extends React.Component {
7-
render() {
8-
return (
9-
<Link to="/history/add/station" id="add-station">
10-
<div className="tile">Add New</div>
11-
</Link>
12-
);
13-
}
14-
}
6+
const AddNewTile = () => (
7+
<Link to="/history/add/station" id="add-station">
8+
<div className="tile">Add New</div>
9+
</Link>
10+
);
11+
12+
export default AddNewTile;

src/components/Pages/AddNew/InstructionText.js

+5-9
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,12 @@ const StyledInstructionH2 = styled.h2`
88
margin: 0 16px 16px 16px;
99
`;
1010

11-
export default class InstructionText extends React.Component {
12-
render() {
13-
return (
14-
<StyledInstructionH2 id="revision-title">
15-
{this.props.text}
16-
</StyledInstructionH2>
17-
);
18-
}
19-
}
11+
const InstructionText = props => (
12+
<StyledInstructionH2 id="revision-title">{props.text}</StyledInstructionH2>
13+
);
2014

2115
InstructionText.propTypes = {
2216
text: PropTypes.string
2317
};
18+
19+
export default InstructionText;

src/components/Pages/AddNew/NewTileInput.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,18 @@ const StyledNewTileInputContainer = styled.div`
1010
margin-top: 100px;
1111
`;
1212

13-
export default class NewTileInput extends React.Component {
14-
render() {
15-
return (
16-
<React.Fragment>
17-
<StyledNewTileInputContainer>
18-
<InstructionText text={this.props.instructionText} />
19-
<NewTileTextInput userTypes={this.props.userTypes} />
20-
</StyledNewTileInputContainer>
21-
</React.Fragment>
22-
);
23-
}
24-
}
13+
const NewTileInput = props => (
14+
<React.Fragment>
15+
<StyledNewTileInputContainer>
16+
<InstructionText text={props.instructionText} />
17+
<NewTileTextInput userTypes={props.userTypes} />
18+
</StyledNewTileInputContainer>
19+
</React.Fragment>
20+
);
2521

2622
NewTileInput.propTypes = {
2723
instructionText: PropTypes.string,
2824
userTypes: PropTypes.func
2925
};
26+
27+
export default NewTileInput;

src/components/Pages/ComingSoon.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@ const ComingSoonImg = styled.img`
1919
max-height: 100%;
2020
`;
2121

22-
export default class ComingSoon extends React.Component {
23-
render() {
24-
return (
25-
<React.Fragment>
26-
<ComingSoonDiv>
27-
<ComingSoonImg id="coming-soon-icon" src={comingSoonIcon} />
28-
</ComingSoonDiv>
29-
<NavBar />
30-
</React.Fragment>
31-
);
32-
}
33-
}
22+
const ComingSoon = () => (
23+
<React.Fragment>
24+
<ComingSoonDiv>
25+
<ComingSoonImg id="coming-soon-icon" src={comingSoonIcon} />
26+
</ComingSoonDiv>
27+
<NavBar />
28+
</React.Fragment>
29+
);
30+
31+
export default ComingSoon;

src/components/Pages/Revision/ResultsContainer.js

+57-18
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,81 @@
11
/* eslint-disable class-methods-use-this */
2+
/* eslint-disable no-invalid-this */
23

34
import React from "react";
45
import PropTypes from "prop-types";
56
import RevisionTitle from "./RevisionTitle";
67
import timerFormat from "../../../utils/timerFormat";
8+
import styled from "styled-components";
9+
10+
const Container = styled.div`
11+
display: flex;
12+
flex-direction: column;
13+
align-items: center;
14+
`;
15+
16+
const ResultsTitle = styled.h3`
17+
font-weight: 700;
18+
margin: 8px 0;
19+
text-transform: uppercase;
20+
`;
21+
22+
const Results = styled.div`
23+
display: flex;
24+
flex-direction: column;
25+
align-items: center;
26+
width: 100vw;
27+
overflow-y: scroll;
28+
`;
29+
30+
const RevisionList = styled.ul`
31+
width: 100%;
32+
`;
33+
34+
const ResultItem = styled.li`
35+
display: flex;
36+
justify-content: center;
37+
height: 48px;
38+
display: flex;
39+
margin: 8px 0;
40+
padding: 8px;
41+
align-items: center;
42+
background-color: white;
43+
`;
44+
45+
const HighlightedItem = styled(ResultItem)`
46+
font-size: 28pt;
47+
font-weight: 700;
48+
color: #009f5c;
49+
`;
750

851
export default class ResultsContainer extends React.Component {
952
render() {
1053
const missed = [...this.props.markSchemeElements]
1154
.filter(mark => !mark.completed)
12-
.map((mark, index) => (
13-
<li key={index} className="result-list-item">
14-
{mark.text}
15-
</li>
16-
));
55+
.map((mark, index) => <ResultItem key={index}>{mark.text}</ResultItem>);
1756
return (
18-
<div id="revision">
57+
<Container>
1958
<RevisionTitle caseTitle={this.props.caseTitle} />
20-
<h3 id="results-h3">Results</h3>
21-
<div id="revision-container">
22-
<ul id="revision-list">
23-
<li className="result-list-item" id="score-percent">
59+
<ResultsTitle>Results</ResultsTitle>
60+
<Results>
61+
<RevisionList>
62+
<HighlightedItem>
2463
{Math.floor(
2564
(this.props.markSchemeCompleted / this.props.markSchemeTotal) *
2665
100
2766
)}
2867
%
29-
</li>
30-
<li className="result-list-item">
68+
</HighlightedItem>
69+
<ResultItem>
3170
You performed {this.props.markSchemeCompleted} of{" "}
3271
{this.props.markSchemeTotal} tasks in{" "}
3372
{timerFormat(this.props.timeElapsed)}.
34-
</li>
35-
</ul>
36-
<h3 id="results-h3">You missed {missed.length} items</h3>
37-
<ul id="revision-list">{missed}</ul>
38-
</div>
39-
</div>
73+
</ResultItem>
74+
</RevisionList>
75+
<ResultsTitle>You missed {missed.length} items</ResultsTitle>
76+
<RevisionList>{missed}</RevisionList>
77+
</Results>
78+
</Container>
4079
);
4180
}
4281
}

src/components/Pages/Revision/RevisionContainer.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const MarkSchemeList = styled.ul`
1818

1919
const MarkSchemeListItem = styled.li`
2020
display: flex;
21-
margin: 4px 0;
22-
padding: 8px 8px;
21+
margin: 4px;
22+
padding: 16px;
2323
align-items: center;
2424
color: ${({ completed }) => completed && "white"};
2525
background-color: ${({ completed }) => (completed ? "#009f5c" : "white")};

0 commit comments

Comments
 (0)