aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorNoah Loomans <noahloomans@gmail.com>2018-02-09 17:04:12 +0100
committerNoah Loomans <noahloomans@gmail.com>2018-02-09 17:04:12 +0100
commit6926de1108b1a084e133d5f8363f080d7c20a99f (patch)
treece27ac5a024e6a0e342037d3ea03ddf7ca6c3c82 /src/client
parent8d5bccc8984618c9282e9592882835819306fa07 (diff)
Use classes instead of stateless functions for Components
Diffstat (limited to 'src/client')
-rw-r--r--src/client/react/components/container/HelpBox.js33
-rw-r--r--src/client/react/components/container/Results.js103
-rw-r--r--src/client/react/components/container/Search.js34
-rw-r--r--src/client/react/components/container/View.js29
-rw-r--r--src/client/react/components/container/WeekSelector.js66
-rw-r--r--src/client/react/components/page/Index.js22
-rw-r--r--src/client/react/components/page/User.js50
-rw-r--r--src/client/react/components/presentational/IconFromUserType.js52
-rw-r--r--src/client/react/components/presentational/Loading.js6
-rw-r--r--src/client/react/components/presentational/Result.js42
-rw-r--r--src/client/react/components/presentational/Schedule.js8
11 files changed, 235 insertions, 210 deletions
diff --git a/src/client/react/components/container/HelpBox.js b/src/client/react/components/container/HelpBox.js
index a74b43c..effe4f5 100644
--- a/src/client/react/components/container/HelpBox.js
+++ b/src/client/react/components/container/HelpBox.js
@@ -2,26 +2,37 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-const HelpBox = ({ results, searchText }) => {
- if (results.length > 0 || searchText !== '') {
- return <div />;
+class HelpBox extends React.Component {
+ static propTypes = {
+ // redux
+ results: PropTypes.arrayOf(PropTypes.string).isRequired,
+ searchText: PropTypes.string.isRequired,
}
- return (
- <div className="help-box">
- <div className="arrow" />
- <div className="bubble">
- Voer hier een docentafkorting, klas, leerlingnummer of lokaalnummer in.
+ render() {
+ if (this.props.results.length > 0 || this.props.searchText !== '') {
+ return <div />;
+ }
+
+ return (
+ <div className="help-box">
+ <div className="arrow" />
+ <div className="bubble">
+ Voer hier een docentafkorting, klas, leerlingnummer of lokaalnummer in.
+ </div>
</div>
- </div>
- );
-};
+ );
+ }
+}
HelpBox.propTypes = {
+ // redux
results: PropTypes.arrayOf(PropTypes.string).isRequired,
searchText: PropTypes.string.isRequired,
};
+HelpBox.propTypes
+
const mapStateToProps = state => ({
results: state.search.results,
searchText: state.search.searchText,
diff --git a/src/client/react/components/container/Results.js b/src/client/react/components/container/Results.js
index f65c0c8..41030e7 100644
--- a/src/client/react/components/container/Results.js
+++ b/src/client/react/components/container/Results.js
@@ -9,66 +9,61 @@ import { setUser } from '../../actions/search';
import { userFromMatch } from '../../lib/url';
import Result from '../presentational/Result';
-const Results = ({
- results,
- searchText,
- selectedResult,
- match,
- history,
- dispatch,
-}) => {
- const user = userFromMatch(match);
+class Results extends React.Component {
+ static propTypes = {
+ results: PropTypes.arrayOf(PropTypes.string).isRequired,
+ searchText: PropTypes.string.isRequired,
+ selectedResult: PropTypes.string,
- const isExactMatch =
- user != null &&
- searchText === users.byId[user].value;
+ // react-router
+ match: PropTypes.object.isRequired,
+ history: PropTypes.object.isRequired,
- return (
- <div
- className={classnames('search__results', {
- 'search__results--has-results': !isExactMatch && results.length > 0,
- })}
- style={{
- minHeight: isExactMatch ? 0 : results.length * 54,
- }}
- >
- {!isExactMatch && results.map(userId => (
- <Result
- key={userId}
- userId={userId}
- isSelected={userId === selectedResult}
- onClick={() => {
- if (userId === user) {
- // EDGE CASE: The user is set if the user changes, but it doesn't
- // change if the result is already the one we are viewing.
- // Therefor, we need to dispatch the SET_USER command manually.
- dispatch(setUser(user));
- } else {
- history.push(`/${userId}`);
- }
- }}
- />
- ))}
- </div>
- );
-};
+ // redux
+ dispatch: PropTypes.func.isRequired,
+ };
-Results.propTypes = {
- results: PropTypes.arrayOf(PropTypes.string).isRequired,
- searchText: PropTypes.string.isRequired,
- selectedResult: PropTypes.string,
+ static defaultProps = {
+ selectedResult: null,
+ };
- // react-router
- match: PropTypes.object.isRequired,
- history: PropTypes.object.isRequired,
+ render() {
+ const user = userFromMatch(this.props.match);
- // redux
- dispatch: PropTypes.func.isRequired,
-};
+ const isExactMatch =
+ user != null &&
+ this.props.searchText === users.byId[user].value;
-Results.defaultProps = {
- selectedResult: null,
-};
+ return (
+ <div
+ className={classnames('search__results', {
+ 'search__results--has-results': !isExactMatch && this.props.results.length > 0,
+ })}
+ style={{
+ minHeight: isExactMatch ? 0 : this.props.results.length * 54,
+ }}
+ >
+ {!isExactMatch && this.props.results.map(userId => (
+ <Result
+ key={userId}
+ userId={userId}
+ isSelected={userId === this.props.selectedResult}
+ onClick={() => {
+ if (userId === user) {
+ // EDGE CASE: The user is set if the user changes, but it doesn't
+ // change if the result is already the one we are viewing.
+ // Therefor, we need to dispatch the SET_USER command manually.
+ this.props.dispatch(setUser(user));
+ } else {
+ this.props.history.push(`/${userId}`);
+ }
+ }}
+ />
+ ))}
+ </div>
+ );
+ }
+}
const mapStateToProps = state => ({
results: state.search.results,
diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js
index 8ca386c..44f2ba1 100644
--- a/src/client/react/components/container/Search.js
+++ b/src/client/react/components/container/Search.js
@@ -14,6 +14,23 @@ import Results from './Results';
import IconFromUserType from '../presentational/IconFromUserType';
class Search extends React.Component {
+ static propTypes = {
+ results: PropTypes.arrayOf(PropTypes.string).isRequired,
+ selectedResult: PropTypes.string,
+ searchText: PropTypes.string.isRequired,
+
+ // react-router
+ match: PropTypes.object.isRequired,
+ history: PropTypes.object.isRequired,
+
+ // redux
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ static defaultProps = {
+ selectedResult: null,
+ };
+
constructor(props) {
super(props);
@@ -132,23 +149,6 @@ class Search extends React.Component {
}
}
-Search.propTypes = {
- results: PropTypes.arrayOf(PropTypes.string).isRequired,
- selectedResult: PropTypes.string,
- searchText: PropTypes.string.isRequired,
-
- // react-router
- match: PropTypes.object.isRequired,
- history: PropTypes.object.isRequired,
-
- // redux
- dispatch: PropTypes.func.isRequired,
-};
-
-Search.defaultProps = {
- selectedResult: null,
-};
-
const mapStateToProps = state => ({
results: state.search.results,
searchText: state.search.searchText,
diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js
index f919dbc..96611d5 100644
--- a/src/client/react/components/container/View.js
+++ b/src/client/react/components/container/View.js
@@ -11,6 +11,20 @@ import Schedule from '../presentational/Schedule';
import Loading from '../presentational/Loading';
class View extends React.Component {
+ static propTypes = {
+ schedules: PropTypes.objectOf(PropTypes.objectOf(PropTypes.shape({
+ state: PropTypes.string.isRequired,
+ htmlStr: PropTypes.string,
+ }))).isRequired,
+
+ // react-router
+ match: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
+
+ // redux
+ dispatch: PropTypes.func.isRequired,
+ };
+
componentDidMount() {
this.fetchScheduleIfNeeded();
}
@@ -49,7 +63,6 @@ class View extends React.Component {
switch (schedule.state) {
case 'NOT_REQUESTED':
- return <Loading />;
case 'FETCHING':
return <Loading />;
case 'FINISHED':
@@ -60,20 +73,6 @@ class View extends React.Component {
}
}
-View.propTypes = {
- schedules: PropTypes.objectOf(PropTypes.objectOf(PropTypes.shape({
- state: PropTypes.string.isRequired,
- htmlStr: PropTypes.string,
- }))).isRequired,
-
- // react-router
- match: PropTypes.object.isRequired,
- location: PropTypes.object.isRequired,
-
- // redux
- dispatch: PropTypes.func.isRequired,
-};
-
const mapStateToProps = state => ({
schedules: state.view.schedules,
});
diff --git a/src/client/react/components/container/WeekSelector.js b/src/client/react/components/container/WeekSelector.js
index b8b5c2b..002128b 100644
--- a/src/client/react/components/container/WeekSelector.js
+++ b/src/client/react/components/container/WeekSelector.js
@@ -10,46 +10,50 @@ import ArrowForwardIcon from 'react-icons/lib/md/arrow-forward';
import purifyWeek from '../../lib/purifyWeek';
import { weekFromLocation } from '../../lib/url';
-function weekName(week) {
- const currentWeek = moment().week();
-
- if (currentWeek === week) {
- return `Huidige week • ${week}`;
- } else if (currentWeek + 1 === week) {
- return `Volgende week • ${week}`;
- } else if (currentWeek - 1 === week) {
- return `Vorige week • ${week}`;
- }
+class WeekSelector extends React.Component {
+ static propTypes = {
+ // react-router
+ location: PropTypes.object.isRequired,
+ history: PropTypes.object.isRequired,
+ };
- return `Week ${week}`;
-}
+ getWeekText() {
+ const week = weekFromLocation(this.props.location);
-const WeekSelector = ({ location, history }) => {
- const week = weekFromLocation(location);
+ const currentWeek = moment().week();
+
+ if (currentWeek === week) {
+ return `Huidige week • ${week}`;
+ } else if (currentWeek + 1 === week) {
+ return `Volgende week • ${week}`;
+ } else if (currentWeek - 1 === week) {
+ return `Vorige week • ${week}`;
+ }
+
+ return `Week ${week}`;
+ }
+
+ updateWeek(change) {
+ const week = weekFromLocation(this.props.location);
- const updateWeek = (change) => {
const newWeek = purifyWeek(week + change);
const isCurrentWeek = moment().week() === newWeek;
const query = queryString.stringify({
week: isCurrentWeek ? undefined : newWeek,
});
- history.push(`${location.pathname}?${query}`);
- };
+ this.props.history.push(`${this.props.location.pathname}?${query}`);
+ }
- return (
- <div className="week-selector">
- <button onClick={() => updateWeek(-1)}><ArrowBackIcon /></button>
- <div className="text">{weekName(week)}</div>
- <button onClick={() => updateWeek(+1)}><ArrowForwardIcon /></button>
- </div>
- );
-};
-
-WeekSelector.propTypes = {
- // react-router
- location: PropTypes.object.isRequired,
- history: PropTypes.object.isRequired,
-};
+ render() {
+ return (
+ <div className="week-selector">
+ <button onClick={() => this.updateWeek(-1)}><ArrowBackIcon /></button>
+ <div className="text">{this.getWeekText()}</div>
+ <button onClick={() => this.updateWeek(+1)}><ArrowForwardIcon /></button>
+ </div>
+ );
+ }
+}
export default withRouter(WeekSelector);
diff --git a/src/client/react/components/page/Index.js b/src/client/react/components/page/Index.js
index e5e47c5..c48ebe0 100644
--- a/src/client/react/components/page/Index.js
+++ b/src/client/react/components/page/Index.js
@@ -2,14 +2,18 @@ import React from 'react';
import Search from '../container/Search';
import HelpBox from '../container/HelpBox';
-const IndexPage = () => (
- <div className="page-index">
- <div className="container">
- <img src="/icons/mml-logo.png" alt="Metis" />
- <Search />
- <HelpBox />
- </div>
- </div>
-);
+class IndexPage extends React.Component {
+ render() {
+ return (
+ <div className="page-index">
+ <div className="container">
+ <img src="/icons/mml-logo.png" alt="Metis" />
+ <Search />
+ <HelpBox />
+ </div>
+ </div>
+ );
+ }
+}
export default IndexPage;
diff --git a/src/client/react/components/page/User.js b/src/client/react/components/page/User.js
index ae1dcc2..b520f90 100644
--- a/src/client/react/components/page/User.js
+++ b/src/client/react/components/page/User.js
@@ -6,34 +6,36 @@ import View from '../container/View';
import { userFromMatch } from '../../lib/url';
import WeekSelector from '../container/WeekSelector';
-const UserPage = ({ match }) => {
- const user = userFromMatch(match);
+class UserPage extends React.Component {
+ static propTypes = {
+ // react-router
+ match: PropTypes.object.isRequired,
+ };
- if (!user) {
- // Invalid user, redirect to index.
- return <Redirect to="/" />;
- }
+ render() {
+ const user = userFromMatch(this.props.match);
+
+ if (!user) {
+ // Invalid user, redirect to index.
+ return <Redirect to="/" />;
+ }
- return (
- <div className="page-user">
- <div className="search-wrapper">
- <div className="search-container">
- <Search />
+ return (
+ <div className="page-user">
+ <div className="search-wrapper">
+ <div className="search-container">
+ <Search />
+ </div>
</div>
- </div>
- <div className="menu">
- <div className="menu-container">
- <WeekSelector />
+ <div className="menu">
+ <div className="menu-container">
+ <WeekSelector />
+ </div>
</div>
+ <View />
</div>
- <View />
- </div>
- );
-};
-
-UserPage.propTypes = {
- // react-router
- match: PropTypes.object.isRequired,
-};
+ );
+ }
+}
export default UserPage;
diff --git a/src/client/react/components/presentational/IconFromUserType.js b/src/client/react/components/presentational/IconFromUserType.js
index ee0e04b..2ef95b7 100644
--- a/src/client/react/components/presentational/IconFromUserType.js
+++ b/src/client/react/components/presentational/IconFromUserType.js
@@ -5,33 +5,35 @@ import RoomIcon from 'react-icons/lib/md/room';
import ClassIcon from 'react-icons/lib/md/group';
import TeacherIcon from 'react-icons/lib/md/account-circle';
-const IconFromUserType = ({ userType, defaultIcon }) => {
- switch (userType) {
- case 'c':
- return <ClassIcon />;
- case 't':
- return <TeacherIcon />;
- case 's':
- return <StudentIcon />;
- case 'r':
- return <RoomIcon />;
- default:
- if (defaultIcon) {
- return defaultIcon;
- }
+class IconFromUserType extends React.Component {
+ static propTypes = {
+ userType: PropTypes.string,
+ defaultIcon: PropTypes.element,
+ };
- throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.');
- }
-};
+ static defaultProps = {
+ userType: null,
+ defaultIcon: null,
+ };
-IconFromUserType.propTypes = {
- userType: PropTypes.string,
- defaultIcon: PropTypes.element,
-};
+ render() {
+ switch (this.props.userType) {
+ case 'c':
+ return <ClassIcon />;
+ case 't':
+ return <TeacherIcon />;
+ case 's':
+ return <StudentIcon />;
+ case 'r':
+ return <RoomIcon />;
+ default:
+ if (this.props.defaultIcon) {
+ return this.props.defaultIcon;
+ }
-IconFromUserType.defaultProps = {
- userType: null,
- defaultIcon: null,
-};
+ throw new Error('`userType` was invalid or not given, but `defaultIcon` is not defined.');
+ }
+ }
+}
export default IconFromUserType;
diff --git a/src/client/react/components/presentational/Loading.js b/src/client/react/components/presentational/Loading.js
index 84eaac7..2ab80da 100644
--- a/src/client/react/components/presentational/Loading.js
+++ b/src/client/react/components/presentational/Loading.js
@@ -1,5 +1,9 @@
import React from 'react';
-const Loading = () => <div>Loading...</div>;
+class Loading extends React.Component {
+ render() {
+ return <div>Loading...</div>;
+ }
+}
export default Loading;
diff --git a/src/client/react/components/presentational/Result.js b/src/client/react/components/presentational/Result.js
index 6d39a80..a30cf52 100644
--- a/src/client/react/components/presentational/Result.js
+++ b/src/client/react/components/presentational/Result.js
@@ -5,25 +5,29 @@ import users from '../../users';
import IconFromUserType from './IconFromUserType';
-const Result = ({ userId, isSelected, onClick }) => (
- // eslint-disable-next-line
- <div
- className={classnames('search__result', {
- 'search__result--selected': isSelected,
- })}
- onClick={onClick}
- >
- <div className="search__icon-wrapper">
- <IconFromUserType userType={users.byId[userId].type} />
- </div>
- <div className="search__result__text">{users.byId[userId].value}</div>
- </div>
-);
+class Result extends React.Component {
+ static propTypes = {
+ userId: PropTypes.string.isRequired,
+ isSelected: PropTypes.bool.isRequired,
+ onClick: PropTypes.func.isRequired,
+ };
-Result.propTypes = {
- userId: PropTypes.string.isRequired,
- isSelected: PropTypes.bool.isRequired,
- onClick: PropTypes.func.isRequired,
-};
+ render() {
+ return (
+ // eslint-disable-next-line
+ <div
+ className={classnames('search__result', {
+ 'search__result--selected': this.props.isSelected,
+ })}
+ onClick={this.props.onClick}
+ >
+ <div className="search__icon-wrapper">
+ <IconFromUserType userType={users.byId[this.props.userId].type} />
+ </div>
+ <div className="search__result__text">{users.byId[this.props.userId].value}</div>
+ </div>
+ );
+ }
+}
export default Result;
diff --git a/src/client/react/components/presentational/Schedule.js b/src/client/react/components/presentational/Schedule.js
index 3f17adc..85cf72f 100644
--- a/src/client/react/components/presentational/Schedule.js
+++ b/src/client/react/components/presentational/Schedule.js
@@ -3,6 +3,10 @@ import PropTypes from 'prop-types';
import createDOMPurify from 'dompurify';
class Schedule extends React.Component {
+ static propTypes = {
+ htmlStr: PropTypes.string.isRequired,
+ };
+
constructor(props) {
super(props);
@@ -58,8 +62,4 @@ class Schedule extends React.Component {
}
}
-Schedule.propTypes = {
- htmlStr: PropTypes.string.isRequired,
-};
-
export default Schedule;