Skip to content
Open
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
23 changes: 23 additions & 0 deletions septa-fare-calculator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
10 changes: 0 additions & 10 deletions septa-fare-calculator/index.html

This file was deleted.

12,348 changes: 12,348 additions & 0 deletions septa-fare-calculator/package-lock.json

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions septa-fare-calculator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "septa-fare-calculator",
"version": "0.1.0",
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@testing-library/react": "^13.3.0",
"react-test-render": "^1.1.2"
}
}
15 changes: 15 additions & 0 deletions septa-fare-calculator/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="Septa Fare Calculator" content="A fare calculator widget" />
<title>SEPTA Fare Calculator</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
13 changes: 13 additions & 0 deletions septa-fare-calculator/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Form from "./component/Form";
function App() {
/*
Hi! I hope you enjoy reading over my code. I really enjoyed making this widget.
*/
return (
<div className="App">
<Form />
</div>
);
}

export default App;
167 changes: 167 additions & 0 deletions septa-fare-calculator/src/component/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {useState, useEffect} from 'react'
import { findZone,formatName, formatCost } from '../helpers/helper_functions';
import { ReactComponent as SepLogo } from "./SEPTA.svg"
import './form.css'

export default function Form() {
const url ='/fares.json'
const [zones, setZones] = useState([]);
const [days, setDays] = useState({});
const [currDay, setCurrDay] = useState('weekday')
const [currZone, setCurrZone] = useState({})
const [checked, setChecked] = useState('advance')
const [purch, setPurch] = useState('advance_purchase')
const [riders, setRiders] = useState('');
const [cost, setCost] = useState(0);

const fetchdata = async ()=> {
const res = await fetch(url);
const data = await res.json()
setZones(data.zones)
setDays(data.info);
setCurrZone(data.zones[0])
}

const handleChange = (e) => {
const name = e.target.name;
const val = e.target.value;
//I created a versatile handleChange function to keep the code DRY
switch (name) {
case "days":
setCurrDay(val)
break;
case "zones":
findZone(zones, Number(val), setCurrZone)
break;
case "purchase":
setChecked(checked === 'advance' ? 'onboard' : 'advance')
setPurch(val)
break;
case "riders":
handleNum(e)
default:
break;
}
}

const handleNum = (e) => {
const regex = /^[0-9\b]+$/;
//This only allows users to type in numbers for the number of riders section
if (e.currentTarget.value === '' || regex.test(e.currentTarget.value)) {
setRiders(Number(e.currentTarget.value));
}
}

const findPrice = (fares, time, location, riders) => {
if (time === 'anytime') {
setCost(fares[4].price);
setRiders(10);
setChecked('advance')
/*
Set the price to a fixed price, update the checked radio button to advanced option, and the riders to 10 because of the special deal for 10 advanced tickets. Functionality does not allow users to change the amount of tickets because the deal is specifically for 10 tickets
*/
} else if (!fares || !time || !location || !riders) {
//Set the cost to 0 unless all the neccesary fields are selected
setCost(0)
} else {
for (let fare of fares) {
const { type, purchase, price } = fare;
/*
iterate over the fare array for the correct zone to find the accurate price for the given parameters(time of day and location of purchase)
*/
if (type === time && purchase === location) {
setCost(price * riders);
return;
}
}
}
}

useEffect(()=> {
//I used an empty dependency array so API is called only once
fetchdata()
}, [])

useEffect(() => {
if (currZone.fares) {
findPrice(currZone.fares, currDay, purch, riders)
}
},[currZone,currDay,purch,riders])

return (
<div className = 'container'>
<header>
<SepLogo />
<h1>Regional Rail Fares</h1>
</header>
<section>
<h3>Where are you going?</h3>
<label htmlFor="zones">
<select name="zones" autoFocus onChange={handleChange}>
{zones.map((zone, i) => (
/*
I made the list of options dynamic in case more zones were added to the railway system
The key is an index rather than set value because the list
of options is static and I am already using the zone# as the value.
Default option is zone one
*/
<option key = {i} value={zone.zone}>{zone.name}</option>
))}
</select>
</label>
</section>
<section>
<h3>When are you riding?</h3>
<label htmlFor="day-options">
<select name="days" id="day-options" value={currDay} onChange = {handleChange} >
{Object.keys(days).slice(0,3).map((day, i) => (
<option key = {i} value = {day}>{formatName(day)}</option>
))}
</select>
</label>
<p className = 'helper'>{currDay === 'anytime' ? `${days[currDay]} (10 ride deal !)` : days[currDay]}</p>
</section>
<section>
<h3>Where will you purchase the fare?</h3>
<div className='radio-btns' role= 'radio-btn container'>
<input
type = "radio"
value = "advance_purchase"
name = "purchase"
id = "advance-purchase"
onChange = {handleChange}
checked = {checked === 'advance'}
aria-checked = {checked === 'advance' ? 'true' : 'false'}
/>
<label htmlFor='advance-purchase'>Station Kiosk</label>
</div>
<div className='radio-btns' role= 'radio-btn container'>
<input
type = "radio"
value = "onboard_purchase"
name = 'purchase'
id = "onboard-purchase"
onChange = {handleChange}
checked ={checked === 'onboard'}
aria-checked = {checked === 'onboard' ? 'true' : 'false'}
/>
<label htmlFor='onboard-purchase'>Onboard</label>
</div>
</section>
<section>
<h3>How many rides will you need?</h3>
<input
type="text"
name = 'riders'
className='number-of-riders'
onChange = {handleNum}
value = {riders}
/>
</section>
<section className = 'cost'>
<h3>Your fare will cost</h3>
<p className='total-amount'><strong>{formatCost.format(cost)}</strong></p>
</section>
</div>
)
}
16 changes: 16 additions & 0 deletions septa-fare-calculator/src/component/SEPTA.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions septa-fare-calculator/src/component/form.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
.container {
border: solid 2px lightgrey;
width: 25rem;
margin: 0 auto;
text-align: center;
}

section {
padding-top: 1em;
padding-bottom: 1em;
border-top: solid 1px lightgray;
}

select {
padding-top: 0.5em;
padding-bottom: 0.5em;
padding-left: 0.2em;
font-size: 1.2rem;
border-radius: 5px;
width: 90%;
border: solid gray 1px;
color: #6B6A69;
}

.number-of-riders {
width: 30%;
margin: 0 auto;
font-size: 1.2rem;
text-align: center;
padding-top: .3;
padding-top: 0.4em;
padding-bottom: 0.4em;
border-radius: 5px;
border: solid gray 1px;
}

header,.cost{
background-color:#3a3b3c;
color:white;
}

header {
display: flex;
align-items: center;
justify-content: center;
}

h1,h3{
text-align:center;
}

h1 {
margin: 0;
padding: 1em;
font-size: 25px;

}
.radio-btns {
text-align: left;
padding-top: 0.5em;
font-size: 18px;
margin-left: 6em;
}
.helper {
font-size: .8em;
color: grey;
}
.total-amount {
font-size: 3rem;
margin: 0;
text-align: center;
}
@media (max-width:490px) {
.container {
width: 100%;
}
}
22 changes: 22 additions & 0 deletions septa-fare-calculator/src/helpers/helper_functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const findZone = (zones, zoneVal,updateZone) => {
for (let oneZone of zones) {
const { zone } = oneZone;
if (zone === zoneVal) {
updateZone(oneZone)
break;
}
}
}

export const formatName = (str) => {
if (str.includes('_')) {
return str.split('_').map(word => word[0].toUpperCase() + word.slice(1)).join(' or ')
} else {
return str[0].toUpperCase() + str.slice(1)
}
}

export const formatCost = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
Loading