Commit 869e5b34 authored by Andrew Hrdy's avatar Andrew Hrdy
Browse files

Convert all class components to functional components

parent df6d22d3
...@@ -2,25 +2,21 @@ import * as React from 'react'; ...@@ -2,25 +2,21 @@ import * as React from 'react';
import * as classNames from 'classnames'; import * as classNames from 'classnames';
import { findLink } from '../utils/nameUtils'; import { findLink } from '../utils/nameUtils';
import { IAlert } from '../models/alert.model'; import { IAlert } from '../models/alert.model';
import Chip from '@material-ui/core/Chip';
import Button from '@material-ui/core/Button';
import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
import { Chip, Button } from '@material-ui/core';
class Alert extends React.Component<AlertProps> { interface AlertProps {
alert: IAlert;
}
constructor(props: AlertProps) { export default ({alert}: AlertProps) => {
super(props);
}
/** /**
* Converts the alert's urgency tag to the corresponding * Converts the alert's urgency tag to the corresponding
* css class. * css class.
*
* @memberof Alert
*/ */
getUrgencyClass = () => { const getUrgencyClass = () => {
switch (this.props.alert.urgency_tag) { switch (alert.urgency_tag) {
case 'emergency': case 'emergency':
return 'alert-emergency'; return 'alert-emergency';
case 'major': case 'major':
...@@ -31,16 +27,14 @@ class Alert extends React.Component<AlertProps> { ...@@ -31,16 +27,14 @@ class Alert extends React.Component<AlertProps> {
default: default:
return 'alert-info'; return 'alert-info';
} }
} };
/** /**
* Converts the alert's text body to proper JSX * Converts the alert's text body to proper JSX
* *
* @memberof Alert * @memberof Alert
*/ */
getBody = () => { const getBody = () => {
const alert: IAlert = this.props.alert;
const links = findLink(alert.body); const links = findLink(alert.body);
if (!links) { if (!links) {
...@@ -58,41 +52,31 @@ class Alert extends React.Component<AlertProps> { ...@@ -58,41 +52,31 @@ class Alert extends React.Component<AlertProps> {
{alert.body.substring(links.index + links[0].length)} {alert.body.substring(links.index + links[0].length)}
</span> </span>
); );
} };
getChipLabel = () => this.props.alert.urgency_tag.charAt(0).toUpperCase() + this.props.alert.urgency_tag.slice(1);
render() { const getChipLabel = () => alert.urgency_tag.charAt(0).toUpperCase() + alert.urgency_tag.slice(1);
const alert: IAlert = this.props.alert;
return ( return (
<div className={'alert'}> <div className={'alert'}>
<div className={'alert-subject-container'}> <div className={'alert-subject-container'}>
<h3 className={'alert-subject'}>{alert.subject}</h3> <h3 className={'alert-subject'}>{alert.subject}</h3>
<Chip label={this.getChipLabel()} className={classNames('alert-urgency-chip', this.getUrgencyClass())} /> <Chip label={getChipLabel()} className={classNames('alert-urgency-chip', getUrgencyClass())} />
</div>
{this.getBody()}
{
alert.url &&
<span className={'alert-url-container'}>
<Button size={'small'} href={alert.url} target="_blank" rel="noopener noreferrer" classes={{
root: 'alert-url-button-root'
}}>
More Information
<ArrowForwardIcon />
</Button>
</span>
}
</div> </div>
);
}
}
export interface AlertProps { {getBody()}
alert: IAlert;
}
export default Alert; {
\ No newline at end of file alert.url &&
<span className={'alert-url-container'}>
<Button size={'small'} href={alert.url} target="_blank" rel="noopener noreferrer" classes={{
root: 'alert-url-button-root'
}}>
More Information
<ArrowForwardIcon />
</Button>
</span>
}
</div>
);
};
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import * as classNames from 'classnames'; import * as classNames from 'classnames';
import SearchBar from '../containers/SearchBar'; import SearchBar from '../containers/SearchBar';
import AlertContainer from '../containers/AlertContainer'; import AlertContainer from '../containers/AlertContainer';
import { AppBar, Toolbar, Typography, Button } from '@material-ui/core';
import AppBar from '@material-ui/core/AppBar'; interface AppBarProps {
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
class CustomAppBar extends React.Component<CustomAppBarProps, CustomAppBarState> {
constructor(props: CustomAppBarProps) {
super(props);
this.state = {
isSearchExpanded: false
};
}
render() {
const {isMobile} = this.props;
return (
<div>
<AppBar position="absolute"
className={classNames('app-bar', this.state.isSearchExpanded && 'app-bar-search-expanded', isMobile && 'app-bar-mobile')}>
<Toolbar className={'app-bar-tool-bar'}>
<div className={'app-bar-logo-name'}>
<img src={'favicon.png'} className={'app-bar-logo'}/>
<Typography variant="h6" className={classNames('app-bar-title', 'app-bar-text-color')}>
What's Open
</Typography>
</div>
<div className={'app-bar-right-section'}>
<div className={'app-bar-alert-container'}>
<AlertContainer/>
</div>
<SearchBar onSearchExpand={() => this.setState({
isSearchExpanded: true
})}
onSearchCollapse={() => this.setState({
isSearchExpanded: false
})}/>
</div>
<div className={'app-bar-link-container'}>
<Button className={classNames('app-bar-link-button', 'app-bar-text-color')}
href={'https://srct.gmu.edu/'} target="_blank" rel="noopener">
About
</Button>
<Button className={classNames('app-bar-link-button', 'app-bar-text-color')}
href={'https://srct.gmu.edu/contact/'} target="_blank" rel="noopener">
Feedback
</Button>
</div>
</Toolbar>
</AppBar>
</div>
);
}
}
export interface CustomAppBarProps {
isMobile?: boolean; isMobile?: boolean;
} }
export interface CustomAppBarState { export default ({isMobile}: AppBarProps) => {
isSearchExpanded: boolean; const [isSearchExpanded, setIsSearchExpanded] = React.useState<boolean>(false);
}
return (
<div>
<AppBar position="absolute"
className={classNames('app-bar', isSearchExpanded && 'app-bar-search-expanded', isMobile && 'app-bar-mobile')}>
<Toolbar className={'app-bar-tool-bar'}>
<div className={'app-bar-logo-name'}>
<img src={'favicon.png'} className={'app-bar-logo'}/>
<Typography variant="h6" className={classNames('app-bar-title', 'app-bar-text-color')}>
What's Open
</Typography>
</div>
<div className={'app-bar-right-section'}>
<div className={'app-bar-alert-container'}>
<AlertContainer/>
</div>
export default CustomAppBar; <SearchBar onSearchExpand={() => setIsSearchExpanded(true)} onSearchCollapse={() => setIsSearchExpanded(false)}/>
</div>
<div className={'app-bar-link-container'}>
<Button className={classNames('app-bar-link-button', 'app-bar-text-color')}
href={'https://srct.gmu.edu/'} target="_blank" rel="noopener">
About
</Button>
<Button className={classNames('app-bar-link-button', 'app-bar-text-color')}
href={'https://srct.gmu.edu/contact/'} target="_blank" rel="noopener">
Feedback
</Button>
</div>
</Toolbar>
</AppBar>
</div>
);
};
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { IFacility, CampusRegion } from '../models/facility.model'; import { IFacility } from '../models/facility.model';
import FacilityCard from '../containers/FacilityCard'; import FacilityCard from '../containers/FacilityCard';
import { Grid } from '@material-ui/core';
import Grid from '@material-ui/core/Grid'; interface CardContainerProps {
class CardContainer extends React.Component<CardContainerProps> {
constructor(props: CardContainerProps) {
super(props);
}
render() {
const {facilities, showFavoriteIcons} = this.props;
return (
<Grid container={true} className={'card-container-root'} spacing={3} justify={'center'} alignItems={'flex-end'}>
{facilities.map(item => (
<Grid key={item.slug} item={true}>
<FacilityCard facility={item} showFavoriteIcon={showFavoriteIcons} />
</Grid>
))}
</Grid>
);
}
}
export interface CardContainerProps {
facilities: IFacility[]; facilities: IFacility[];
showFavoriteIcons: boolean; showFavoriteIcons: boolean;
} }
export default CardContainer; export default ({facilities, showFavoriteIcons}: CardContainerProps) => {
\ No newline at end of file return (
<Grid container={true} className={'card-container-root'} spacing={3} justify={'center'} alignItems={'flex-end'}>
{facilities.map(item => (
<Grid key={item.slug} item={true}>
<FacilityCard facility={item} showFavoriteIcon={showFavoriteIcons} />
</Grid>
))}
</Grid>
);
};
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { IFacilityCategory } from '../models/facility.model'; import { IFacilityCategory } from '../models/facility.model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
class FacilityCategory extends React.Component<FacilityCategoryProps> {
constructor(props: FacilityCategoryProps) {
super(props);
}
render() {
return (
<div className={'facility-category-wrapper'}>
<Typography variant={'body1'} noWrap={true}>
{this.props.category.name}
</Typography>
</div>
);
}
}
export interface FacilityCategoryProps { export interface FacilityCategoryProps {
category: IFacilityCategory; category: IFacilityCategory;
} }
export default FacilityCategory; export default ({category}: FacilityCategoryProps) => {
\ No newline at end of file return (
<div className={'facility-category-wrapper'}>
<Typography variant={'body1'} noWrap={true}>
{category.name}
</Typography>
</div>
);
};
\ No newline at end of file
...@@ -2,25 +2,23 @@ import * as React from 'react'; ...@@ -2,25 +2,23 @@ import * as React from 'react';
import * as classNames from 'classnames'; import * as classNames from 'classnames';
import FacilityUtils from '../utils/facilityUtils'; import FacilityUtils from '../utils/facilityUtils';
import { IFacility } from '../models/facility.model'; import { IFacility } from '../models/facility.model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography'; interface FacilityStatusProps {
facility: IFacility;
class FacilityStatus extends React.Component<FacilityStatusProps> { }
constructor(props: FacilityStatusProps) {
super(props);
}
export default ({facility}: FacilityStatusProps) => {
/** /**
* Generates information about the facility's status. * Generates information about the facility's status.
* *
* @returns {{label: string, isOpen: boolean}} Information about the facility. * @returns {{label: string, isOpen: boolean}} Information about the facility.
*/ */
generateStatusInfo = () => { const generateStatusInfo = () => {
let label; let label;
let isOpen; let isOpen;
if (FacilityUtils.isFacilityOpen(this.props.facility)) { if (FacilityUtils.isFacilityOpen(facility)) {
label = 'OPEN'; label = 'OPEN';
isOpen = true; isOpen = true;
} else { } else {
...@@ -32,22 +30,14 @@ class FacilityStatus extends React.Component<FacilityStatusProps> { ...@@ -32,22 +30,14 @@ class FacilityStatus extends React.Component<FacilityStatusProps> {
label: label, label: label,
isOpen: isOpen isOpen: isOpen
}; };
} };
render() {
const statusInfo = this.generateStatusInfo();
return ( const statusInfo = generateStatusInfo();
<Typography variant={'caption'}
className={classNames('facility-status-text', statusInfo.isOpen ? 'facility-status-open' : 'facility-status-closed')}>
{statusInfo.label}
</Typography>
);
}
}
export interface FacilityStatusProps {
facility: IFacility;
}
export default FacilityStatus; return (
\ No newline at end of file <Typography variant={'caption'}
className={classNames('facility-status-text', statusInfo.isOpen ? 'facility-status-open' : 'facility-status-closed')}>
{statusInfo.label}
</Typography>
);
};
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import * as classNames from 'classnames'; import * as classNames from 'classnames';
import { Tooltip } from '@material-ui/core';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'; import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import FavoriteIcon from '@material-ui/icons/Favorite'; import FavoriteIcon from '@material-ui/icons/Favorite';
import Tooltip from '@material-ui/core/Tooltip';
import { trackPiwikEvent } from '../piwik/piwik'; import { trackPiwikEvent } from '../piwik/piwik';
class FavoriteButton extends React.Component<FavoriteButtonProps, FavoriteButtonState> { interface FavoriteButtonProps {
slug: string;
initialState: boolean;
addFavoriteFacility: (slug: string) => void;
removeFavoriteFacility: (slug: string) => void;
}
constructor(props: FavoriteButtonProps) { export default ({slug, initialState, addFavoriteFacility, removeFavoriteFacility}: FavoriteButtonProps) => {
super(props); const [isFavorite, setIsFavorite] = React.useState<boolean>(initialState);
this.state = { const handleClick = (event: React.MouseEvent) => {
isFavorite: props.initialState
};
}
handleClick = (event: React.MouseEvent) => {
event.stopPropagation(); // Stops the card from being selected in the sidebar. event.stopPropagation(); // Stops the card from being selected in the sidebar.
event.preventDefault(); // Also stops the card from being selected in the sidebar. event.preventDefault(); // Also stops the card from being selected in the sidebar.
const newState = !this.state.isFavorite; const newState = !isFavorite;
this.setState({ setIsFavorite(newState);
isFavorite: newState
});
if (!newState) { if (!newState) {
trackPiwikEvent('card-action', 'un-favorite'); trackPiwikEvent('card-action', 'un-favorite');
setTimeout(() => { setTimeout(() => {
this.props.removeFavoriteFacility(this.props.slug); removeFavoriteFacility(slug);
}, 0); }, 0);
} else { } else {
trackPiwikEvent('card-action', 'favorite'); trackPiwikEvent('card-action', 'favorite');
setTimeout(() => { setTimeout(() => {
this.props.addFavoriteFacility(this.props.slug); addFavoriteFacility(slug);
}, 0); }, 0);
} }
} };
render() {
if (this.state.isFavorite) {
return (
<Tooltip title="Remove Favorite">
<FavoriteIcon onClick={this.handleClick}
className={classNames('favorite-button-heart', 'favorite-button-heart-favorited')} />
</Tooltip>
);
}
if (isFavorite) {
return ( return (
<Tooltip title="Add Favorite"> <Tooltip title="Remove Favorite">
<FavoriteBorderIcon onClick={this.handleClick} <FavoriteIcon onClick={handleClick}
className={classNames('favorite-button-heart')} /> className={classNames('favorite-button-heart', 'favorite-button-heart-favorited')} />
</Tooltip> </Tooltip>
); );
} }
}
export interface FavoriteButtonProps {
slug: string;
initialState: boolean;
addFavoriteFacility: (slug: string) => void;
removeFavoriteFacility: (slug: string) => void;
}
interface FavoriteButtonState {
isFavorite: boolean;
}
export default FavoriteButton; return (
\ No newline at end of file <Tooltip title="Add Favorite">
<FavoriteBorderIcon onClick={handleClick}
className={classNames('favorite-button-heart')} />
</Tooltip>
);
};
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { CircularProgress, Typography } from '@material-ui/core'; import { CircularProgress, Typography } from '@material-ui/core';
const LoadingSpinner = () => ( export default () => (
<div className={'loading-spinner-container'}> <div className={'loading-spinner-container'}>
<CircularProgress className={'loading-spinner'} /> <CircularProgress className={'loading-spinner'} />
<Typography className={'loading-text'} variant={'h6'}> <Typography className={'loading-text'} variant={'h6'}>
Loading Loading
</Typography> </Typography>
</div> </div>
); );
\ No newline at end of file
export default LoadingSpinner;
\ No newline at end of file
...@@ -2,66 +2,48 @@ import * as React from 'react'; ...@@ -2,66 +2,48 @@ import * as React from 'react';
import { removeBrackets } from '../utils/nameUtils'; import { removeBrackets } from '../utils/nameUtils';
import * as phoneFormatter from 'phone-formatter'; import * as phoneFormatter from 'phone-formatter';
import { IFacility } from '../models/facility.model'; import { IFacility } from '../models/facility.model';
import TextwTitle from './TextwTitle'; import TextwTitle from './TextwTitle';
import WeekHours from './WeekHours'; import WeekHours from './WeekHours';
import { Paper, Avatar, Typography, Divider, IconButton } from '@material-ui/core';
import Paper from '@material-ui/core/Paper';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';