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';
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,22 +52,19 @@ 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())} />
<Chip label={getChipLabel()} className={classNames('alert-urgency-chip', getUrgencyClass())} />
</div>
{this.getBody()}
{getBody()}
{
alert.url &&
......@@ -88,11 +79,4 @@ class Alert extends React.Component<AlertProps> {
}
</div>
);
}
}
export interface AlertProps {
alert: IAlert;
}
export default Alert;
\ No newline at end of file
};
\ 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
};
}
interface AppBarProps {
isMobile?: boolean;
}
render() {
const {isMobile} = this.props;
export default ({isMobile}: AppBarProps) => {
const [isSearchExpanded, setIsSearchExpanded] = React.useState<boolean>(false);
return (
<div>
<AppBar position="absolute"
className={classNames('app-bar', this.state.isSearchExpanded && 'app-bar-search-expanded', isMobile && 'app-bar-mobile')}>
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'}/>
......@@ -38,12 +28,7 @@ class CustomAppBar extends React.Component<CustomAppBarProps, CustomAppBarState>
<AlertContainer/>
</div>
<SearchBar onSearchExpand={() => this.setState({
isSearchExpanded: true
})}
onSearchCollapse={() => this.setState({
isSearchExpanded: false
})}/>
<SearchBar onSearchExpand={() => setIsSearchExpanded(true)} onSearchCollapse={() => setIsSearchExpanded(false)}/>
</div>
<div className={'app-bar-link-container'}>
......@@ -60,15 +45,4 @@ class CustomAppBar extends React.Component<CustomAppBarProps, CustomAppBarState>
</AppBar>
</div>
);
}
}
export interface CustomAppBarProps {
isMobile?: boolean;
}
export interface CustomAppBarState {
isSearchExpanded: boolean;
}
export default CustomAppBar;
};
\ 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;
interface CardContainerProps {
facilities: IFacility[];
showFavoriteIcons: boolean;
}
export default ({facilities, showFavoriteIcons}: CardContainerProps) => {
return (
<Grid container={true} className={'card-container-root'} spacing={3} justify={'center'} alignItems={'flex-end'}>
{facilities.map(item => (
......@@ -23,12 +18,4 @@ class CardContainer extends React.Component<CardContainerProps> {
))}
</Grid>
);
}
}
export interface CardContainerProps {
facilities: IFacility[];
showFavoriteIcons: boolean;
}
export default CardContainer;
\ No newline at end of file
};
\ No newline at end of file
import * as React from 'react';
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);
}
export interface FacilityCategoryProps {
category: IFacilityCategory;
}
render() {
export default ({category}: FacilityCategoryProps) => {
return (
<div className={'facility-category-wrapper'}>
<Typography variant={'body1'} noWrap={true}>
{this.props.category.name}
{category.name}
</Typography>
</div>
);
}
}
export interface FacilityCategoryProps {
category: IFacilityCategory;
}
export default FacilityCategory;
\ No newline at end of file
};
\ 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,10 +30,9 @@ class FacilityStatus extends React.Component<FacilityStatusProps> {
label: label,
isOpen: isOpen
};
}
};
render() {
const statusInfo = this.generateStatusInfo();
const statusInfo = generateStatusInfo();
return (
<Typography variant={'caption'}
......@@ -43,11 +40,4 @@ class FacilityStatus extends React.Component<FacilityStatusProps> {
{statusInfo.label}
</Typography>
);
}
}
export interface FacilityStatusProps {
facility: IFacility;
}
export default FacilityStatus;
\ No newline at end of file
};
\ 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> {
constructor(props: FavoriteButtonProps) {
super(props);
interface FavoriteButtonProps {
slug: string;
initialState: boolean;
addFavoriteFacility: (slug: string) => void;
removeFavoriteFacility: (slug: string) => void;
}
this.state = {
isFavorite: props.initialState
};
}
export default ({slug, initialState, addFavoriteFacility, removeFavoriteFacility}: FavoriteButtonProps) => {
const [isFavorite, setIsFavorite] = React.useState<boolean>(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;
const newState = !isFavorite;
setIsFavorite(newState);
this.setState({
isFavorite: 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) {
if (isFavorite) {
return (
<Tooltip title="Remove Favorite">
<FavoriteIcon onClick={this.handleClick}
<FavoriteIcon onClick={handleClick}
className={classNames('favorite-button-heart', 'favorite-button-heart-favorited')} />
</Tooltip>
);
......@@ -54,22 +50,8 @@ class FavoriteButton extends React.Component<FavoriteButtonProps, FavoriteButton
return (
<Tooltip title="Add Favorite">
<FavoriteBorderIcon onClick={this.handleClick}
<FavoriteBorderIcon onClick={handleClick}
className={classNames('favorite-button-heart')} />
</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
};
\ 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'}>
......@@ -9,5 +9,3 @@ const LoadingSpinner = () => (
</Typography>
</div>
);
\ No newline at end of file
export default LoadingSpinner;
\ No newline at end of file
......@@ -2,28 +2,19 @@ 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);
}
import * as classNames from 'classnames';
render() {
const {facility, isVisible, closeSidebar} = this.props;
interface SidebarProps {
facility: IFacility;
isVisible: boolean;
closeSidebar: () => void;
}
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')}>
......@@ -55,13 +46,4 @@ class Sidebar extends React.Component<SidebarProps> {
</Paper>
</div>
);
}
}
export interface SidebarProps {
facility: IFacility;
isVisible: boolean;
closeSidebar: () => void;
}
export default Sidebar;
\ No newline at end of file
};
\ No newline at end of file
import * as React from 'react';
class TextwTitle extends React.Component<TextwTitleProps> {
constructor(props: TextwTitleProps) {
super(props);
}
export interface TextwTitleProps {
label: string;
content: any;
}
render() {
export default ({label, content}: TextwTitleProps) => {
return (
<div>
<div className={'text-w-title-label'}>
{this.props.label}
{label}
</div>
<div className={'text-w-title-content'}>
{this.props.content}
{content}
</div>
</div>
);
}
}
export interface TextwTitleProps {
label: string;
content: any;
}
export default TextwTitle;
\ No newline at end of file
};
\ No newline at end of file
import * as React from 'react';
import facilityUtils from '../utils/facilityUtils';
import { IFacility } from '../models/facility.model';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { Grid, Typography } from '@material-ui/core';
const weekDays = [
'Mon',
......@@ -15,12 +13,11 @@ const weekDays = [
'Sun'
];
export class WeekHours extends React.Component<WeekHoursProps> {
constructor(props: WeekHoursProps) {
super(props);
}
interface WeekHoursProps {
facility: IFacility;
}
render() {
export default ({facility}: WeekHoursProps) => {
const output = [];
/*
......@@ -31,11 +28,11 @@ export class WeekHours extends React.Component<WeekHoursProps> {
let index = 0;
for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
const todaysHours = facilityUtils.getHoursByDay(this.props.facility, dayOfWeek);
const todaysHours = facilityUtils.getHoursByDay(facility, dayOfWeek);
for (let i = 0; i < todaysHours.length; i++) {
output[index] = (
<Grid container={true} spacing={0} key={this.props.facility.slug + index} className="week-hours-row">
<Grid container={true} spacing={0} key={facility.slug + index} className="week-hours-row">
<Grid item={true} xs={4}>
<Typography variant={'body1'}>{weekDays[dayOfWeek]}</Typography>
</Grid>
......@@ -56,11 +53,4 @@ export class WeekHours extends React.Component<WeekHoursProps> {
{output}
</div>
);
}
}
export interface WeekHoursProps {
facility: IFacility;
}
export default WeekHours;
\ No newline at end of file
};
\ No newline at end of file
import * as React from 'react';
import { connect } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import { IAlert } from '../models/alert.model';
import Alert from '../components/Alert';
import IconButton from '@material-ui/core/IconButton';
import Popover from '@material-ui/core/Popover';
import { IconButton, Popover, Typography } from '@material-ui/core';
import NotificationsIcon from '@material-ui/icons/Notifications';
import CloseIcon from '@material-ui/icons/Close';
import Typography from '@material-ui/core/Typography';
import { ApplicationState } from '../store';
import { addViewedAlerts } from '../store/alert/alert.actions';
import { Dispatch } from 'redux';
import { addViewedAlertsAction } from '../store/alert/alert.actions';
class AlertContainer extends React.Component<AlertContainerProps, AlertContainerState> {
export default () => {
const alerts: IAlert[] = useSelector((state: ApplicationState) => state.alerts.alerts);
const viewedAlerts: number[] = useSelector((state: ApplicationState) => state.alerts.viewedAlerts);
constructor(props: AlertContainerProps) {
super(props);
const dispatch = useDispatch();
const addViewedAlerts = (alertIds: number[]) => dispatch(addViewedAlertsAction(alertIds));
this.state = {
isOpen: false,
anchorEl: null
};
}
const [isOpen, setIsOpen] = React.useState<boolean>(false);
const [anchorEl, setAnchorEl] = React.useState<HTMLElement>(null);
handleOpen = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
this.setState({
isOpen: true,
anchorEl: event.currentTarget
});
const handleOpen = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
setIsOpen(true);
setAnchorEl(event.currentTarget);
this.props.addViewedAlerts(this.props.alerts.map(alert => alert.id));
}
addViewedAlerts(alerts.map(alert => alert.id));
};
handleClose = () => {
this.setState({
isOpen: false,
anchorEl: null
});