Commit 9142ec18 authored by Mattias J Duffy's avatar Mattias J Duffy

Merge branch 'development' into 'master'

Version 2.2.0

Closes #48, #45, #17, #39, #44, #26, and #37

See merge request !8
parents 3d7f91d2 1d51fc46
Pipeline #2872 passed with stage
in 3 minutes and 6 seconds
......@@ -20,6 +20,7 @@
"plugin:react/recommended"
],
"rules": {
"no-unused-vars": "warn",
"no-console": "off",
"no-case-declarations": "off",
"no-template-curly-in-string": "warn",
......@@ -43,7 +44,11 @@
"error",
4,
{
"SwitchCase": 1
"SwitchCase": 1,
"ignoredNodes": [
"JSXAttribute",
"JSXSpreadAttribute"
]
}
],
"jsx-quotes": "error",
......@@ -123,6 +128,7 @@
"react/no-typos": "warn",
"react/jsx-indent": "error",
"react/jsx-pascal-case": "error",
"react/jsx-wrap-multilines": "warn"
"react/jsx-wrap-multilines": "warn",
"react/no-deprecated": "off"
}
}
\ No newline at end of file
image: node:6.5.0
image: node:8.11.2
stages:
- build
......@@ -7,11 +7,10 @@ stages:
build:
stage: build
variables:
API_GET_FACILITIES: "https://api.srct.gmu.edu/whatsopen/v2/facilities/"
API_GET_FACILITIES: "'https://api.srct.gmu.edu/whatsopen/v2/facilities/'"
script:
- npm install -g yarn
- yarn install
- CI=false REACT_APP_API_GET_FACILITIES=$API_GET_FACILITIES npm run build
- yarn
- CI=false API_GET_FACILITIES=$API_GET_FACILITIES npm run build
artifacts:
paths:
- build
......@@ -19,11 +18,10 @@ build:
build_shopmason:
stage: build
variables:
API_GET_FACILITIES: "https://api.srct.gmu.edu/whatsopen/v2/facilities/?facility_classifier=shopmason"
API_GET_FACILITIES: "'https://api.srct.gmu.edu/whatsopen/v2/facilities/?facility_classifier=shopmason'"
script:
- npm install -g yarn
- yarn install
- CI=false REACT_APP_API_GET_FACILITIES=$API_GET_FACILITIES npm run build
- yarn
- CI=false API_GET_FACILITIES=$API_GET_FACILITIES npm run build
artifacts:
paths:
- build
......@@ -36,7 +34,7 @@ deploy_staging:
name: staging
url: https://whatsopen.gmu.io
only:
- 2.1-dev
- development
deploy_production:
stage: deploy
......
# Changelog
## [2.2.0] - 2018-09-05
### Added
- Route for Apple app site association
- Map location dropdown
- Map support for all campuses
- Close button for the map dialog
- Custom Webpack
### Removed
- Create React App
### Changed
- README updated to not be the React default
### Fixed
- The clear button now works properly on mobile
- The search bar is focused when the search button is pressed on mobile
- The favorite icon now shows on mobile
## [2.1.4] - 2018-08-28
### Added
......@@ -68,4 +92,5 @@
[2.1.1]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1-Midnight-Cherry...v2.1.1
[2.1.2]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.1...v2.1.2
[2.1.3]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.2...v2.1.3
[2.1.4]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.3...v2.1.4
\ No newline at end of file
[2.1.4]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.3...v2.1.4
[2.2.0]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.4...v2.2.0
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
......@@ -5,48 +5,75 @@
"private": true,
"dependencies": {
"array-sort": "^0.1.4",
"autosuggest-highlight": "^3.1.0",
"classnames": "^2.2.5",
"fuzzysearch": "^1.0.3",
"history": "^4.6.3",
"immutability-helper": "^2.3.0",
"mapbox": "^1.0.0-beta9",
"mapbox-gl": "^0.40.1",
"material-ui": "^1.0.0-beta.25",
"material-ui-icons": "^1.0.0-alpha.19",
"node-sass-chokidar": "^0.0.3",
"npm-run-all": "^4.1.1",
"material-ui": "1.0.0-beta.25",
"material-ui-icons": "1.0.0-alpha.19",
"phone-formatter": "^0.0.2",
"promise": "7.1.1",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-autosuggest": "^9.3.2",
"react-classnames": "^0.1.2",
"react-dom": "^15.6.1",
"react-mapbox-gl": "^2.5.2",
"react-notification-system": "^0.2.16",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.2",
"react-router-redux": "^5.0.0-alpha.9",
"react-scripts": "1.0.10",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"typeface-roboto": "0.0.50"
},
"devDependencies": {
"babel-eslint": "^7.2.3",
"eslint-plugin-react": "^7.5.1"
"autoprefixer": "7.1.1",
"babel": "^6.23.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.4",
"eslint": "4.19.1",
"eslint-loader": "^2.0.0",
"eslint-plugin-react": "^7.8.2",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.4.0",
"node-sass": "^4.9.0",
"optimize-css-assets-webpack-plugin": "^4.0.2",
"postcss-loader": "2.0.6",
"sass-loader": "^7.0.2",
"style-loader": "^0.18.2",
"sw-precache-webpack-plugin": "0.11.3",
"uglifyjs-webpack-plugin": "^1.2.6",
"webpack": "^4.4.1",
"webpack-cli": "^2.1.5",
"webpack-dev-server": "^3.1.4"
},
"proxy": "http://localhost:3001",
"scripts": {
"build-css": "node-sass-chokidar src/styles/ -o src/styles/build",
"watch-css": "node-sass-chokidar src/styles/ -o src/styles/build --watch --recursive",
"start-js": "react-scripts start",
"start": "npm-run-all -p build-css watch-css start-js",
"build-js": "react-scripts build",
"build": "npm-run-all build-css build-js",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public"
"build": "webpack --config webpack/webpack.config.prod.js",
"start": "webpack-dev-server --config webpack/webpack.config.dev.js --open"
},
"babel": {
"presets": [
[
"env",
{
"modules": false
}
],
"react"
],
"plugins": [
"transform-class-properties"
]
},
"eslintConfig": {
"extends": "react"
}
}
{
"webcredentials": {
"apps": [
"K5MMVK2UFR.edu.gmu.srct.whatsopen"
]
},
"applinks": {
"apps": [],
"details": [
{
"appID": "K5MMVK2UFR.edu.gmu.srct.whatsopen",
"paths": [
"/"
]
}
]
}
}
\ No newline at end of file
......@@ -28,8 +28,8 @@
<meta name="twitter:title" content="What's Open">
<meta name="twitter:description" content="Facility hours for George Mason University">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png">
<link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" href="./favicon.png">
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.40.1/mapbox-gl.css' rel='stylesheet' />
</head>
......
import {GET_ALERTS, GET_FACILITIES, SET_ALERTS, SET_FACILITIES, SORT_FACILITY_CARDS, VIEW_ALERT} from './action-types';
const API_GET_FACILITIES = process.env.REACT_APP_API_GET_FACILITIES ? process.env.REACT_APP_API_GET_FACILITIES :
const API_GET_FACILITIES = process.env.API_GET_FACILITIES ? process.env.API_GET_FACILITIES :
'https://api.srct.gmu.edu/whatsopen/v2/facilities/';
const API_GET_ALERTS = 'https://api.srct.gmu.edu/whatsopen/v2/alerts/?ordering=urgency_tag';
export const getFacilities = () => (dispatch) => {
......
......@@ -29,7 +29,7 @@ const Alert = ({alert}) => {
return (
<span className={'alert-message'}>
{alert.message.substring(0, links.index)}
<a href={links[0]} className={'alert-link'} target="_blank" rel="noopener">{links[0]}</a>
<a href={links[0]} className={'alert-link'} target="_blank" rel="noopener noreferrer">{links[0]}</a>
{alert.message.substring(links.index + links[0].length)}
</span>
);
......
This diff is collapsed.
......@@ -10,7 +10,6 @@ import MapDialog from './MapDialog';
import CloseIcon from 'material-ui-icons/Close';
import IconButton from 'material-ui/IconButton';
import LocationOnIcon from 'material-ui-icons/LocationOn';
// import {getMaxBounds} from '../utils/mapboxUtils';
import {removeBrackets} from '../utils/nameUtils';
class FacilityDialog extends React.Component {
......@@ -19,13 +18,11 @@ class FacilityDialog extends React.Component {
super(props);
this.state = {
isMapOpen: false,
maxBounds: props.maxBounds,
isMapOpen: false
};
}
toggleMap = (e) => {
e.stopPropagation();
toggleMap = () => {
this.setState({
isMapOpen: !this.state.isMapOpen
});
......@@ -33,16 +30,8 @@ class FacilityDialog extends React.Component {
render() {
const {facility, facilities, isOpen, onClose} = this.props;
const {isMapOpen, maxBounds} = this.state;
const {isMapOpen} = this.state;
let mapCenter, mapZoom;
try {
mapCenter = facility.facility_location.coordinate_location.coordinates;
mapZoom = [17];
} catch (e) {
mapCenter = [(maxBounds[0][0] + maxBounds[1][0]) / 2, (maxBounds[0][1] + maxBounds[1][1]) / 2];
mapZoom = [13];
}
return (
<Dialog classes={{
......@@ -91,9 +80,8 @@ class FacilityDialog extends React.Component {
<MapDialog
open={isMapOpen}
facilities={facilities}
maxBounds={maxBounds}
zoom={mapZoom}
center={mapCenter}
facility={facility}
fullScreen={true}
onClose={this.toggleMap}
/>
</Dialog>
......
import React from 'react';
import ReactMapboxGl, {Marker} from 'react-mapbox-gl';
import mapboxgl from 'mapbox-gl';
import PropTypes from 'prop-types';
import Dialog from 'material-ui/Dialog';
const mapboxToken = 'pk.eyJ1IjoibWR1ZmZ5OCIsImEiOiJjaXk2a2lxODQwMDdyMnZzYTdyb3M4ZTloIn0.mSocl7zUnZBO6-CV9cvmnA';
const Map = ReactMapboxGl({
accessToken: mapboxToken,
attributionControl: false
});
import FacilitiesMap from './FacilitiesMap';
import IconButton from 'material-ui/IconButton';
import CloseIcon from 'material-ui-icons/Close';
class MapDialog extends React.Component {
handleRequestClose = () => {
......@@ -17,48 +11,25 @@ class MapDialog extends React.Component {
};
render() {
const {facilities, zoom, center, maxBounds, ...other} = this.props;
const {facility, facilities, campusRegion, open, width, height, fullScreen = false} = this.props;
return (
<Dialog onClose={this.handleRequestClose} {...other}>
<Map
onStyleLoad={(map) => {
map.addControl(new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
}));
}}
onClick={() => {
this.setState({
mapDialogOpen: true
});
}}
style="mapbox://styles/mduffy8/cjbcdxi3v73hp2spiyhxbkjde"
movingMethod={'easeTo'}
containerStyle={{
height: '500px',
width: '600px',
borderRadius: '5px'
}}
center={center}
zoom={zoom}
maxBounds={maxBounds}>
{(facilities.length > 0) && facilities.map((item) => {
return (
<Marker
key={item.slug}
coordinates={item.facility_location.coordinate_location.coordinates}
anchor="bottom">
<img style={{
objectFit: 'contain'
}} height={40} width={40} src={item.logo} />
</Marker>
);
})}
</Map>
<Dialog onClose={this.handleRequestClose} open={open} fullScreen={fullScreen}>
<div style={{
height: height || '100%',
width: width || '100%'
}}>
<IconButton className={'map-dialog-close-btn'} onClick={this.handleRequestClose}>
<CloseIcon />
</IconButton>
<FacilitiesMap
facilities={facilities}
facility={facility}
interactive={true}
campusRegion={campusRegion}
/>
</div>
</Dialog>
);
}
......
......@@ -5,6 +5,7 @@ import Typography from 'material-ui/Typography';
import Divider from 'material-ui/Divider';
import TextwTitle from '../components/TextwTitle';
import FacilitiesMap from '../components/FacilitiesMap';
import MapDialog from '../components/MapDialog';
import classNames from 'classnames';
import WeekHours from './WeekHours';
import FacilityLabels from './FacilityLabels';
......@@ -13,48 +14,84 @@ import IconButton from 'material-ui/IconButton';
import {removeBrackets} from '../utils/nameUtils';
import phoneFormatter from 'phone-formatter';
const Sidebar = ({facility, isSidebarOpen, facilities, setSidebar, setSelectedFacility, campusRegion}) => {
class Sidebar extends React.Component {
const handleSidebarClose = () => {
setSelectedFacility(null);
setSidebar(false);
constructor(props) {
super(props);
this.state = {
mapDialogOpen: false
};
}
handleSidebarClose = () => {
this.props.setSelectedFacility(null);
this.props.setSidebar(false);
};
handleMapDialogClose = () => {
this.setState({
mapDialogOpen: false
});
};
return (
<div
className={classNames(['card-container-offset', (isSidebarOpen && 'card-container-offset-open'), (!isSidebarOpen && 'card-container-offset-closed')])}>
<meta></meta>
<Paper
className={classNames(['sidebar-root', (isSidebarOpen && 'sidebar-open'), (!isSidebarOpen && 'sidebar-closed')])}>
<IconButton className={'sidebar-close-btn'} onClick={handleSidebarClose}>
<CloseIcon />
</IconButton>
<div className={'sidebar-row1'}>
<Avatar className={'sidebar-avatar'} src={facility.logo} />
<div className={'sidebar-title'}>
<Typography type="display1">{removeBrackets(facility.facility_name)}</Typography>
render() {
const {facility, isSidebarOpen, facilities} = this.props;
const {mapDialogOpen} = this.state;
return (
<div
className={classNames(['card-container-offset', (isSidebarOpen && 'card-container-offset-open'), (!isSidebarOpen && 'card-container-offset-closed')])}>
<meta></meta>
<Paper
className={classNames(['sidebar-root', (isSidebarOpen && 'sidebar-open'), (!isSidebarOpen && 'sidebar-closed')])}>
<IconButton className={'sidebar-close-btn'} onClick={this.handleSidebarClose}>
<CloseIcon />
</IconButton>
<div className={'sidebar-row1'}>
<Avatar className={'sidebar-avatar'} src={facility.logo} />
<div className={'sidebar-title'}>
<Typography type="display1">{removeBrackets(facility.facility_name)}</Typography>
</div>
</div>
</div>
<Divider className={'sidebar-divider'} />
<div className={'sidebar-scroll'}>
<div className={'sidebar-label-holder'}>
<TextwTitle label="Building"
content={facility.facility_location && facility.facility_location.building} />
<TextwTitle label="Address"
content={facility.facility_location && facility.facility_location.address} />
<TextwTitle label="Phone Number"
content={facility.phone_number ? phoneFormatter.format(facility.phone_number, "(NNN) NNN-NNNN") : 'Unknown'} />
<TextwTitle label="Labels" content={<FacilityLabels facility={facility} />} />
<TextwTitle label="Hours" content={<WeekHours facility={facility} />} />
<Divider className={'sidebar-divider'} />
<div className={'sidebar-scroll'}>
<div className={'sidebar-label-holder'}>
<TextwTitle label="Building"
content={facility.facility_location && facility.facility_location.building} />
<TextwTitle label="Address"
content={facility.facility_location && facility.facility_location.address} />
<TextwTitle label="Phone Number"
content={facility.phone_number ? phoneFormatter.format(facility.phone_number, '(NNN) NNN-NNNN') : 'Unknown'} />
<TextwTitle label="Labels" content={<FacilityLabels facility={facility} />} />
<TextwTitle label="Hours" content={<WeekHours facility={facility} />} />
</div>
</div>
</div>
<div className={'sidebar-row2'}>
<FacilitiesMap facilities={facilities} facility={facility} campusRegion={campusRegion} />
</div>
</Paper>
</div>
);
};
export default Sidebar;
<div className={'sidebar-row2'}>
<div className={'sidebar-map-container'} onClick={() => {
this.setState({
mapDialogOpen: true
});
}}>
<FacilitiesMap
facilities={facilities}
facility={facility}
interactive={false}
/>
</div>
<MapDialog
open={mapDialogOpen}
facilities={facilities}
facility={facility}
height={'500px'}
width={'600px'}
onClose={this.handleMapDialogClose}
/>
</div>
</Paper>
</div>
);
}
}
export default Sidebar;
\ No newline at end of file
......@@ -51,8 +51,7 @@ class Layout extends React.Component {
</div>
<Sidebar facilities={facilities} facility={selectedFacility} isSidebarOpen={isSidebarOpen}
setSidebar={setSidebar} setSelectedFacility={setSelectedFacility}
campusRegion={campusRegion}/>
setSidebar={setSidebar} setSelectedFacility={setSelectedFacility}/>
</div>
</div>
);
......
......@@ -61,7 +61,12 @@ class SearchBar extends React.Component {
this.props.onSearchExpand();
this.inputElement.focus();
/*
This timeout is necessary because inputElement is not displayed when execution reaches this point.
By using setTimeout with no delay, inputElement.focus() is added to the end of the execution stack, therefore
being executed after the input is visible.,
*/
setTimeout(() => this.inputElement.focus());
};
handleMobileCollapse = () => {
......
......@@ -11,8 +11,11 @@ import ReduxThunk from 'redux-thunk';
import reducers from './reducers/index';
import {MuiThemeProvider} from 'material-ui/styles';
import theme from './theme';
import './styles/build/whatsOpen.css';
import './styles/whatsOpen.scss';
import 'typeface-roboto';
import '../public/manifest.json';
import '../public/favicon.png';
import '../public/apple-app-site-association';
// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory();
......
import {GET_ALERTS, GET_FACILITIES, SET_ALERTS, SET_FACILITIES, SORT_FACILITY_CARDS, VIEW_ALERT } from '../actions/action-types';
import {GET_ALERTS, GET_FACILITIES, SET_ALERTS, SET_FACILITIES, SORT_FACILITY_CARDS, VIEW_ALERT} from '../actions/action-types';
import cloneDeep from 'lodash/cloneDeep';
import facilityUtils from '../utils/facilityUtils';
const defaultFacilityState = {
......
.facilities-map-campus-select {
position: absolute !important;
z-index: 100;
left: 50%;
transform: translateX(-50%);
top: 10px;
height: 30px !important;
background-color: white !important;
border-radius: 5px;
box-shadow: 0 0 0 2px rgba(0,0,0,0.1);
}
.facilities-map-popup-list {
margin: 0;
padding-left: 16px;
max-height: 100px;
overflow-y: auto;
}
\ No newline at end of file
......@@ -11,15 +11,7 @@
}
.favorite-button-heart-favorited {
color: #E91E63; //TODO: Somehow use materials color system
}
.favorite-button-heart-hover {
color: grey;
}
.favorite-button-heart-no-hover {
color: rgba(0, 0, 0, 0);
color: #E91E63 !important; //TODO: Somehow use materials color system
}
//Between lg and xl
......@@ -31,9 +23,22 @@
}
}
//Above lg
@media screen and (min-width: map-get($breakpoints, lg)) {
.favorite-button-heart-hover {
color: grey;
}
.favorite-button-heart-no-hover {
color: rgba(0, 0, 0, 0);
}
}
//Under lg
@media screen and (max-width: map-get($breakpoints, lg)) {
@media screen and (max-width: map-get($breakpoints, lg) - 1px) {
.favorite-button-heart {
color: grey;
height: 24px * $favorite-button-sm-scale !important;
width: 24px * $favorite-button-sm-scale !important;
padding: 5px * $favorite-button-sm-scale !important;
......
.map-dialog-close-btn {
position: absolute !important;
z-index: 100;
top: 10px;
left: 10px;
width: 30px !important;
height: 30px !important;
background-color: white !important;
border-radius: 5px !important;
box-shadow: 0 0 0 2px rgba(0,0,0,0.1);
}
\ No newline at end of file
......@@ -93,6 +93,14 @@
width: 100%;
}
.sidebar-map-container {
transition: 250ms ease-in-out;
width: 100%;
height: 200px;
padding: 10px;
box-sizing: border-box;
}
.sidebar-toggle-map-btn {
width: 100%;
}
......
......@@ -41,17 +41,10 @@
}
.search-bar-campus-control {
// margin-right: 8px !important;
width: 150px;
justify-content: center;
}
.search-bar-has-value {
.search-bar-close-btn {
display: inline-flex !important;
}
}
@media screen and (min-width: map-get($breakpoints, lg)) {
.search-bar-paper-background {
&.search-bar-focus {
......@@ -64,6 +57,12 @@
.search-bar-input {
transition: ease-in-out width 250ms;
}
.search-bar-has-value {
.search-bar-close-btn {
display: inline-flex !important;
}
}
}
@media screen and (max-width: map-get($breakpoints, lg) - 1px) {
......@@ -76,9 +75,10 @@
width: 100% !important;
}
}
.hide-search-input {
display: none !important;
}