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

Convert all class components to functional components

parent df6d22d3
......@@ -2,25 +2,21 @@ import * as React from 'react';
import * as classNames from 'classnames';
import { findLink } from '../utils/nameUtils';
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 { Chip, Button } from '@material-ui/core';
class Alert extends React.Component<AlertProps> {
interface AlertProps {
alert: IAlert;
}
constructor(props: AlertProps) {
super(props);
}
export default ({alert}: AlertProps) => {
/**
* Converts the alert's urgency tag to the corresponding
* css class.
*
* @memberof Alert
*/
getUrgencyClass = () => {
switch (this.props.alert.urgency_tag) {
const getUrgencyClass = () => {
switch (alert.urgency_tag) {
case 'emergency':
return 'alert-emergency';
case 'major':
......@@ -31,16 +27,14 @@ class Alert extends React.Component<AlertProps> {
default:
return 'alert-info';
}
}
};
/**
* Converts the alert's text body to proper JSX
*
* @memberof Alert
*/
getBody = () => {
const alert: IAlert = this.props.alert;
const getBody = () => {
const links = findLink(alert.body);
if (!links) {
......@@ -58,41 +52,31 @@ class Alert extends React.Component<AlertProps> {
{alert.body.substring(links.index + links[0].length)}
</span>
);
}
getChipLabel = () => this.props.alert.urgency_tag.charAt(0).toUpperCase() + this.props.alert.urgency_tag.slice(1);
};
render() {
const alert: IAlert = this.props.alert;
const getChipLabel = () => alert.urgency_tag.charAt(0).toUpperCase() + alert.urgency_tag.slice(1);
return (
<div className={'alert'}>
<div className={'alert-subject-container'}>
<h3 className={'alert-subject'}>{alert.subject}</h3>
<Chip label={this.getChipLabel()} className={classNames('alert-urgency-chip', this.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>
}
return (
<div className={'alert'}>
<div className={'alert-subject-container'}>
<h3 className={'alert-subject'}>{alert.subject}</h3>
<Chip label={getChipLabel()} className={classNames('alert-urgency-chip', getUrgencyClass())} />
</div>
);
}
}
export interface AlertProps {
alert: IAlert;
}
{getBody()}
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 classNames from 'classnames';
import SearchBar from '../containers/SearchBar';
import AlertContainer from '../containers/AlertContainer';
import { AppBar, Toolbar, Typography, Button } from '@material-ui/core';
import AppBar from '@material-ui/core/AppBar';
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 {
interface AppBarProps {
isMobile?: boolean;
}
export interface CustomAppBarState {
isSearchExpanded: boolean;
}
export default ({isMobile}: AppBarProps) => {
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 { IFacility, CampusRegion } from '../models/facility.model';
import { IFacility } from '../models/facility.model';
import FacilityCard from '../containers/FacilityCard';
import { Grid } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
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 {
interface CardContainerProps {
facilities: IFacility[];
showFavoriteIcons: boolean;
}
export default CardContainer;
\ No newline at end of file
export default ({facilities, showFavoriteIcons}: CardContainerProps) => {
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 { IFacilityCategory } from '../models/facility.model';
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>
);
}
}
import { Typography } from '@material-ui/core';
export interface FacilityCategoryProps {
category: IFacilityCategory;
}
export default FacilityCategory;
\ No newline at end of file
export default ({category}: FacilityCategoryProps) => {
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';
import * as classNames from 'classnames';
import FacilityUtils from '../utils/facilityUtils';
import { IFacility } from '../models/facility.model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
class FacilityStatus extends React.Component<FacilityStatusProps> {
constructor(props: FacilityStatusProps) {
super(props);
}
interface FacilityStatusProps {
facility: IFacility;
}
export default ({facility}: FacilityStatusProps) => {
/**
* Generates information about the facility's status.
*
* @returns {{label: string, isOpen: boolean}} Information about the facility.
*/
generateStatusInfo = () => {
const generateStatusInfo = () => {
let label;
let isOpen;
if (FacilityUtils.isFacilityOpen(this.props.facility)) {
if (FacilityUtils.isFacilityOpen(facility)) {
label = 'OPEN';
isOpen = true;
} else {
......@@ -32,22 +30,14 @@ class FacilityStatus extends React.Component<FacilityStatusProps> {
label: label,
isOpen: isOpen
};
}
render() {
const statusInfo = this.generateStatusInfo();
};
return (
<Typography variant={'caption'}
className={classNames('facility-status-text', statusInfo.isOpen ? 'facility-status-open' : 'facility-status-closed')}>
{statusInfo.label}
</Typography>
);
}
}
export interface FacilityStatusProps {
facility: IFacility;
}
const statusInfo = generateStatusInfo();
export default FacilityStatus;
\ No newline at end of file
return (
<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 classNames from 'classnames';
import { Tooltip } from '@material-ui/core';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import FavoriteIcon from '@material-ui/icons/Favorite';
import Tooltip from '@material-ui/core/Tooltip';
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) {
super(props);
export default ({slug, initialState, addFavoriteFacility, removeFavoriteFacility}: FavoriteButtonProps) => {
const [isFavorite, setIsFavorite] = React.useState<boolean>(initialState);
this.state = {
isFavorite: props.initialState
};
}
handleClick = (event: React.MouseEvent) => {
const handleClick = (event: React.MouseEvent) => {
event.stopPropagation(); // 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;
this.setState({
isFavorite: newState
});
const newState = !isFavorite;
setIsFavorite(newState);
if (!newState) {
trackPiwikEvent('card-action', 'un-favorite');
setTimeout(() => {
this.props.removeFavoriteFacility(this.props.slug);
removeFavoriteFacility(slug);
}, 0);
} else {
trackPiwikEvent('card-action', 'favorite');
setTimeout(() => {
this.props.addFavoriteFacility(this.props.slug);
addFavoriteFacility(slug);
}, 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 (
<Tooltip title="Add Favorite">
<FavoriteBorderIcon onClick={this.handleClick}
className={classNames('favorite-button-heart')} />
<Tooltip title="Remove Favorite">
<FavoriteIcon onClick={handleClick}
className={classNames('favorite-button-heart', 'favorite-button-heart-favorited')} />
</Tooltip>
);
}
}
export interface FavoriteButtonProps {
slug: string;
initialState: boolean;
addFavoriteFacility: (slug: string) => void;
removeFavoriteFacility: (slug: string) => void;
}
interface FavoriteButtonState {
isFavorite: boolean;
}
export default FavoriteButton;
\ No newline at end of file
return (
<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 { CircularProgress, Typography } from '@material-ui/core';
const LoadingSpinner = () => (
export default () => (
<div className={'loading-spinner-container'}>
<CircularProgress className={'loading-spinner'} />
<Typography className={'loading-text'} variant={'h6'}>
Loading
</Typography>
</div>
);
export default LoadingSpinner;
\ No newline at end of file
);
\ No newline at end of file
......@@ -2,66 +2,48 @@ import * as React from 'react';
import { removeBrackets } from '../utils/nameUtils';
import * as phoneFormatter from 'phone-formatter';
import { IFacility } from '../models/facility.model';
import TextwTitle from './TextwTitle';
import WeekHours from './WeekHours';
import Paper from '@material-ui/core/Paper';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';
import Divider from '@material-ui/core/Divider';
import { Paper, Avatar, Typography, Divider, IconButton } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton';
import Slide from '@material-ui/core/Slide';
import classNames = require('classnames');
class Sidebar extends React.Component<SidebarProps> {
constructor(props: SidebarProps) {
super(props);
}
render() {
const {facility, isVisible, closeSidebar} = this.props;
return (
<div className={classNames('sidebar-container', isVisible && 'sidebar-container-open')}>
<Paper className={classNames('sidebar-root', isVisible ? 'sidebar-root-open' : 'sidebar-root-closed')}>
<IconButton onClick={closeSidebar} className={'sidebar-close-btn'}>
<CloseIcon />
</IconButton>
<div className={'sidebar-row1'}>
<Avatar className={'sidebar-avatar'} src={facility?.logo} />
<div className={'sidebar-title'}>
<Typography variant="h4">{removeBrackets(facility?.facility_name)}</Typography>
</div>
</div>
import * as classNames from 'classnames';
<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="Hours" content={<WeekHours facility={facility} />} />
</div>
</div>
</Paper>
</div>
);
}
}
export interface SidebarProps {
interface SidebarProps {
facility: IFacility;
isVisible: boolean;
closeSidebar: () => void;
}
export default Sidebar;
\ No newline at end of file
export default ({facility, isVisible, closeSidebar}: SidebarProps) => {
return (
<div className={classNames('sidebar-container', isVisible && 'sidebar-container-open')}>
<Paper className={classNames('sidebar-root', isVisible ? 'sidebar-root-open' : 'sidebar-root-closed')}>
<IconButton onClick={closeSidebar} className={'sidebar-close-btn'}>
<CloseIcon />
</IconButton>
<div className={'sidebar-row1'}>
<Avatar className={'sidebar-avatar'} src={facility?.logo} />
<div className={'sidebar-title'}>
<Typography variant="h4">{removeBrackets(facility?.facility_name)}</Typography>
</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="Hours" content={<WeekHours facility={facility} />} />
</div>
</div>
</Paper>
</div>
);
};
\ No newline at end of file
import * as React from 'react';
class TextwTitle extends React.Component<TextwTitleProps> {
constructor(props: TextwTitleProps) {
super(props);